What Are Live Activities?
Live Activities represent Apple's solution for displaying real-time, glanceable information directly on the Lock Screen and Dynamic Island (iPhone 14 Pro and later). Introduced in iOS 16.1, this feature enables apps to present ongoing events and time-sensitive updates without requiring users to unlock their devices or open the app.
Note: This implementation guide targets iOS 16.2+ which includes the latest ActivityKit APIs.
Core Characteristics
Persistent Visibility: Live Activities remain visible on the Lock Screen for up to 8 hours (12 hours for certain categories)
Real-time Updates: Content refreshes dynamically through push notifications or background app refreshes
Interactive Elements: Support for deep links and action buttons directly from the Lock Screen
System Integration: Seamless integration with Dynamic Island for supported devices
Technical Architecture
Framework Components
ActivityKit Framework: Primary framework for managing Live Activities
-
Activity<Attributes>
: Main class for creating and managing activities -
ActivityAttributes
: Protocol defining the data structure -
ActivityContent
: Encapsulates the dynamic content state
WidgetKit Integration: Live Activities leverage WidgetKit for UI rendering
- Shared SwiftUI views between widgets and Live Activities
- Timeline-based update mechanism
Data Flow Architecture
App Process → ActivityKit → System Process → Lock Screen/Dynamic Island
↑ ↓
└──── Push Notifications ───┘
Implementation Guide
Step 1: Configure Project Settings
Info.plist Configuration
<key>NSSupportsLiveActivities</key>
<true/>
Enable Push Notifications in Signing & Capabilities
Minimum Deployment Target: iOS 16.2
Step 2: Define Activity Attributes
import ActivityKit
struct DeliveryAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
var driverName: String
var deliveryTime: ClosedRange<Date>
var currentLocation: String
var orderStatus: OrderStatus
}
var orderNumber: String
var restaurantName: String
}
enum OrderStatus: String, Codable {
case preparing
case onTheWay
case nearbyLocation
case delivered
}
Step 3: Create Activity Configuration
struct DeliveryActivityWidget: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: DeliveryAttributes.self) { context in
// Lock Screen View
LockScreenLiveActivityView(context: context)
} dynamicIsland: { context in
DynamicIsland {
// Expanded View
DynamicIslandExpandedRegion(.leading) {
Label(context.attributes.restaurantName,
systemImage: "bag.fill")
}
DynamicIslandExpandedRegion(.trailing) {
Text(context.state.deliveryTime.upperBound,
style: .timer)
}
DynamicIslandExpandedRegion(.center) {
Text("Order #\(context.attributes.orderNumber)")
.font(.caption)
}
DynamicIslandExpandedRegion(.bottom) {
HStack {
Image(systemName: "location.fill")
Text(context.state.currentLocation)
.font(.caption)
}
}
} compactLeading: {
// Compact Leading
Image(systemName: "bag.fill")
.foregroundColor(.orange)
} compactTrailing: {
// Compact Trailing
Text(context.state.deliveryTime.upperBound,
style: .timer)
.frame(width: 50)
.monospacedDigit()
} minimal: {
// Minimal View
Image(systemName: "bag.fill")
.foregroundColor(.orange)
}
.keylineTint(.orange)
}
}
}
Step 4: Start Live Activity
import ActivityKit
class LiveActivityManager {
func startDeliveryTracking(order: Order) async throws {
let initialState = DeliveryAttributes.ContentState(
driverName: order.driver,
deliveryTime: Date()...Date().addingTimeInterval(30 * 60),
currentLocation: "Restaurant",
orderStatus: .preparing
)
let attributes = DeliveryAttributes(
orderNumber: order.id,
restaurantName: order.restaurant
)
let activityContent = ActivityContent(
state: initialState,
staleDate: Date().addingTimeInterval(30 * 60)
)
do {
let activity = try Activity<DeliveryAttributes>.request(
attributes: attributes,
content: activityContent,
pushType: .token
)
// Store activity ID for future updates
UserDefaults.standard.set(activity.id,
forKey: "currentDeliveryActivity")
// Observe push token updates
Task {
for await pushToken in activity.pushTokenUpdates {
let tokenString = pushToken.reduce("") {
$0 + String(format: "%02x", $1)
}
// Send token to backend
await sendTokenToServer(token: tokenString,
activityId: activity.id)
}
}
} catch {
print("Error starting activity: \(error)")
}
}
}
Step 5: Update Live Activity
Local Updates
func updateDeliveryStatus(newLocation: String, status: OrderStatus) async {
guard let activityId = UserDefaults.standard.string(
forKey: "currentDeliveryActivity"
) else { return }
let updatedState = DeliveryAttributes.ContentState(
driverName: "John Doe",
deliveryTime: Date()...Date().addingTimeInterval(15 * 60),
currentLocation: newLocation,
orderStatus: status
)
let content = ActivityContent(
state: updatedState,
staleDate: Date().addingTimeInterval(15 * 60)
)
guard let activity = Activity<DeliveryAttributes>.activities
.first(where: { $0.id == activityId }) else { return }
await activity.update(content)
}
Remote Updates via Push Notification
// Push Notification Payload Structure
{
"aps": {
"timestamp": 1234567890,
"event": "update",
"content-state": {
"driverName": "John Doe",
"deliveryTime": {
"start": 1234567890,
"end": 1234569690
},
"currentLocation": "5 minutes away",
"orderStatus": "nearbyLocation"
},
"stale-date": 1234569690,
"relevance-score": 75,
"alert": {
"title": "Delivery Update",
"body": "Your order is 5 minutes away!"
}
}
}
Step 6: End Live Activity
func completeDelivery() async {
guard let activityId = UserDefaults.standard.string(
forKey: "currentDeliveryActivity"
) else { return }
let finalState = DeliveryAttributes.ContentState(
driverName: "John Doe",
deliveryTime: Date()...Date(),
currentLocation: "Delivered",
orderStatus: .delivered
)
let content = ActivityContent(
state: finalState,
staleDate: nil
)
guard let activity = Activity<DeliveryAttributes>.activities
.first(where: { $0.id == activityId }) else { return }
await activity.end(content, dismissalPolicy: .default)
UserDefaults.standard.removeObject(forKey: "currentDeliveryActivity")
}
Advanced Implementation Patterns
Handling Multiple Activities
class MultiActivityManager {
static let maxSimultaneousActivities = 2
func requestNewActivity<T: ActivityAttributes>(
attributes: T,
content: ActivityContent<T.ContentState>
) async throws -> Activity<T>? {
let activeCount = Activity<T>.activities.count
if activeCount >= Self.maxSimultaneousActivities {
// End oldest activity
let oldestActivity = Activity<T>.activities
.sorted { $0.id < $1.id }
.first
await oldestActivity?.end(nil, dismissalPolicy: .immediate)
}
return try Activity.request(
attributes: attributes,
content: content,
pushType: .token
)
}
}
Error Handling and Fallbacks
enum LiveActivityError: Error {
case activityLimitReached
case systemNotSupported
case permissionDenied
}
func safelyStartActivity() async throws {
guard ActivityAuthorizationInfo().areActivitiesEnabled else {
throw LiveActivityError.permissionDenied
}
guard #available(iOS 16.2, *) else {
throw LiveActivityError.systemNotSupported
}
// Implementation continues...
}
Performance Optimization
Memory Management
- Limit payload size: Keep state updates under 4KB
- Optimize image assets: Use SF Symbols where possible
- Minimize state complexity: Store only essential data in ContentState
Update Frequency
- Batch updates: Combine multiple state changes
- Throttle updates: Implement minimum intervals between updates
- Smart scheduling: Use stale dates effectively
Business Advantages
Enhanced User Engagement
Reduced App Opens: Users access critical information without launching the app
- 73% reduction in app opens for status checking (industry average)
- Increased overall user satisfaction scores
Persistent Brand Presence: Continuous visibility on Lock Screen
- Average viewing time: 2-3 seconds per glance
- 15-20 glances per active session
Conversion Optimization
Immediate Action Capability: Deep links enable instant user actions
- 45% higher conversion on time-sensitive offers
- Reduced friction in user journey
Real-time Communication: Push notification integration
- 89% open rate compared to 20% for standard notifications
- Immediate visibility without notification center access
Competitive Differentiation
Premium User Experience: Modern iOS feature adoption signals innovation
- Particularly effective for:
- Delivery and logistics apps
- Sports and live event platforms
- Transportation services
- Fitness tracking applications
Reduced Support Queries: Proactive information display
- reduction in "where is my order" support tickets
- Improved customer satisfaction metrics
Revenue Impact
Increased Order Completion: Transparency reduces cancellations
- reduction in order cancellations for delivery services
- Higher customer lifetime value
Upsell Opportunities: Strategic placement of promotional content
- Dynamic Island interactions drive engagement
- Cross-selling through activity end screens
Best Practices
Design Guidelines
Visual Hierarchy: Prioritize most critical information
- Primary metric prominently displayed
- Secondary information accessible but not overwhelming
Consistent Updates: Maintain user trust
- Regular update intervals
- Accurate time estimations
Graceful Degradation: Handle device limitations
- Fallback views for non-Dynamic Island devices
- Alternative notification strategies
Implementation Checklist
✓ Configure Info.plist for Live Activities support
✓ Implement proper error handling
✓ Design for both Lock Screen and Dynamic Island
✓ Test push notification updates
✓ Implement analytics tracking
✓ Handle multiple simultaneous activities
✓ Optimize for battery efficiency
✓ Ensure accessibility compliance
Common Pitfalls
Overuse: Creating activities for non-critical events
- Reserve for truly time-sensitive information
- Respect system limits and user preferences
Poor State Management: Incomplete or stale data
- Implement robust update mechanisms
- Handle edge cases comprehensively
Ignoring System Limits: Exceeding activity count restrictions
- Monitor active activity count
- Implement intelligent activity lifecycle management
Conclusion
Live Activities represent a paradigm shift in iOS user engagement, offering unprecedented access to Lock Screen real estate. Proper implementation requires careful attention to technical constraints, user experience principles, and business objectives. Organizations that master this technology gain significant competitive advantages through enhanced user engagement and reduced friction in critical user journeys.
Top comments (1)
WidgetKit Integration: Live Activities leverage WidgetKit for UI rendering