Developing Live Activities for iOS
Live Activities — iOS 16.1+ feature allowing app to display dynamic content directly on lock screen and Dynamic Island (iPhone 14 Pro+). Taxi coming to you — progress bar on Lock Screen updates in real time. Match in progress — score in Dynamic Island without unlocking phone.
Live up to 8 hours (or 12 with explicit extension via ActivityKit), then system dismisses them. After 12 hours — forced termination.
ActivityKit: Architecture
Live Activity built on three things: ActivityAttributes (static data set on start), ContentState (dynamic data updated via push or API), and SwiftUI View for three representations: Lock Screen / Standby, Dynamic Island Compact, Dynamic Island Expanded.
struct DeliveryAttributes: ActivityAttributes {
// Static — doesn't change during life
let orderId: String
let restaurantName: String
struct ContentState: Codable, Hashable {
// Dynamic — updates
var status: DeliveryStatus
var estimatedMinutes: Int
var courierLocation: CLLocationCoordinate2D?
}
}
Start from app:
let attributes = DeliveryAttributes(orderId: "1234", restaurantName: "South Pizza")
let state = DeliveryAttributes.ContentState(status: .preparing, estimatedMinutes: 30)
let content = ActivityContent(state: state, staleDate: Date().addingTimeInterval(600))
let activity = try Activity.request(
attributes: attributes,
content: content,
pushType: .token // Get push token for remote updates
)
staleDate — date after which content is stale and system can show outdatedness indicator. For taxi: Date() + estimatedMinutes * 60.
Update via Push Notifications
Most reliable way to update Live Activity from server — ActivityKit Push Notifications (not regular APNs).
Push payload:
{
"aps": {
"timestamp": 1698765432,
"event": "update",
"content-state": {
"status": "in_delivery",
"estimatedMinutes": 12
},
"alert": {
"title": "Courier left",
"body": "Expect in 12 minutes"
}
}
}
event: "end" — terminate Live Activity via push. alert in push for Live Activity — displayed as Dynamic Island update with haptic feedback.
Push token for Live Activity differs from regular APNs token. Get via activity.pushTokenUpdates AsyncSequence: need to listen for updates, token can change.
Task {
for await tokenData in activity.pushTokenUpdates {
let token = tokenData.map { String(format: "%02x", $0) }.joined()
await sendTokenToServer(token)
}
}
SwiftUI Views for Different Zones
struct DeliveryLiveActivityView: View {
let context: ActivityViewContext<DeliveryAttributes>
var body: some View {
// Lock Screen / StandBy
HStack {
Image(systemName: statusIcon(context.state.status))
VStack(alignment: .leading) {
Text(context.attributes.restaurantName)
.font(.headline)
Text("~\(context.state.estimatedMinutes) min")
.font(.subheadline)
}
Spacer()
DeliveryProgressView(status: context.state.status)
}
.padding()
}
}
DynamicIsland builder for Dynamic Island:
DynamicIsland {
// Expanded — long tap
DynamicIslandExpandedRegion(.leading) {
Image(systemName: "bicycle")
}
DynamicIslandExpandedRegion(.trailing) {
Text("\(context.state.estimatedMinutes) min")
}
DynamicIslandExpandedRegion(.bottom) {
Text("Courier: \(courierName)")
}
} compactLeading: {
Image(systemName: "bicycle")
} compactTrailing: {
Text("\(context.state.estimatedMinutes)m")
} minimal: {
Image(systemName: "bicycle")
}
Compact — two zones beside "island" in collapsed state. Minimal — one small icon when multiple Live Activities active.
Constraints to Know
ContentState size — max 4KB in ActivityKit push payload. For courier coordinates: don't send entire track, only current position.
App must be foreground or have Background Push to start Live Activity. Can't start from background without user action (from iOS 17.2 — can via ActivityAuthorizationInfo().areActivitiesEnabled).
Simulator supports Live Activities from iOS 16.2 Simulator. Dynamic Island — only on physical iPhone 14 Pro/Pro Max and newer.
Timeframe: 3-5 days for basic implementation with push-updates. Cost depends on UI complexity and server part presence.







