π Delivery Tracking Live Activity
λ§λ€κΈ°
Build a Live Activity using ActivityKit to track delivery status on the Lock Screen and Dynamic Island in real-time.
π What is Live Activity?
Live Activity the real-time status of ongoing tasks on the Lock Screen and Dynamic Island. Unlike widgets, they have a time limit (max 8 hours) with real-time update capability.
β
Good Examples
Delivery/taxi tracking, sports scores, timers, music playback, flight info
β Bad Examples
Weather (β Widget), Calendar reminders (β Notification), Stocks (β Widget)
ποΈ Define ActivityAttributes
import ActivityKit struct DeliveryAttributes: ActivityAttributes { // Static: λ³κ²½ λΆκ° let orderNumber: String let storeName: String // ContentState: μ€μκ° μ λ°μ΄νΈ struct ContentState: Codable, Hashable { let status: DeliveryStatus let estimatedArrival: Date let driverName: String? } } enum DeliveryStatus: String, Codable { case preparing // μ€λΉ μ€ case pickedUp // ν½μ μλ£ case nearby // κ·Όμ² λμ°© case delivered // λ°°λ¬ μλ£ }
ποΈ Dynamic Island Layout
Dynamic Island has 3 states: Compact, Minimal, and Expanded.
struct DeliveryLiveActivity: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: DeliveryAttributes.self) { context in // μ κΈ νλ©΄ LockScreenView(context: context) } dynamicIsland: { context in DynamicIsland { // Expanded DynamicIslandExpandedRegion(.leading) { Image(systemName: "person.circle") } DynamicIslandExpandedRegion(.trailing) { Image(systemName: context.state.status.symbol) } DynamicIslandExpandedRegion(.center) { Text(context.attributes.storeName) } DynamicIslandExpandedRegion(.bottom) { ProgressView(value: context.state.progress) } } compactLeading: { Image(systemName: "bicycle") } compactTrailing: { Text(context.state.estimatedArrival, style: .timer) } minimal: { Image(systemName: "bicycle") } } } }
π± Lock Screen View Implementation
struct LockScreenView: View { let context: ActivityViewContext<DeliveryAttributes> var body: some View { HStack(spacing: 12) { // μν μμ΄μ½ Image(systemName: context.state.status.symbol) .font(.title2) .foregroundStyle(.blue) VStack(alignment: .leading, spacing: 4) { Text(context.attributes.storeName) .font(.headline) Text(context.state.status.description) .font(.caption) .foregroundStyle(.secondary) // λμ°© μμ μκ° Text(context.state.estimatedArrival, style: .relative) .font(.caption2) .foregroundStyle(.blue) } Spacer() // μ§νλ₯ ProgressView(value: context.state.progress) .progressViewStyle(.circular) } .padding(16) } }
β‘ Activity Lifecycle
class DeliveryActivityManager { private var currentActivity: Activity<DeliveryAttributes>? // 1. Activity μμ func startActivity(orderNumber: String, storeName: String) async throws { let attributes = DeliveryAttributes( orderNumber: orderNumber, storeName: storeName ) let initialState = DeliveryAttributes.ContentState( status: .preparing, estimatedArrival: Date().addingTimeInterval(1800), driverName: nil ) currentActivity = try Activity.request( attributes: attributes, content: ActivityContent(state: initialState, staleDate: nil), pushType: .token // Push Notification μ§μ ) } // 2. μν μ λ°μ΄νΈ func updateStatus(_ status: DeliveryStatus, driverName: String? = nil) async { let newState = DeliveryAttributes.ContentState( status: status, estimatedArrival: Date().addingTimeInterval(600), driverName: driverName ) await currentActivity?.update( ActivityContent(state: newState, staleDate: nil) ) } // 3. Activity μ’ λ£ func endActivity() async { let finalState = DeliveryAttributes.ContentState( status: .delivered, estimatedArrival: Date(), driverName: nil ) await currentActivity?.end( ActivityContent(state: finalState, staleDate: nil), dismissalPolicy: .after(Date().addingTimeInterval(3600)) // 1μκ° ν μλ μ κ±° ) } }
βοΈ Info.plist Configuration
To use Live Activity, add the permission to Info.plist:
<key>NSSupportsLiveActivities</key> <true/>
π Push Notification Integration (Optional)
To update remotely from a server, use Push Tokens:
// Push Token κ°μ Έμ€κΈ° for await pushToken in Activity<DeliveryAttributes>.pushToStartTokenUpdates { // μλ²λ‘ Push Token μ μ‘ await sendTokenToServer(pushToken) }
β οΈ Important Notes
β±οΈ Time Limit
Displayed for up to 8 hours only. Automatically removed after that.
π Battery Impact
Frequent updates drain battery. A minimum 5-second interval is recommended.
π± Device Limitations
Dynamic Island is only displayed on iPhone 14 Pro and later. On other devices, it only shows on the Lock Screen.
π¨ UI Constraints
Tap gestures, animations, and video are not supported. Button, Toggle, and TextField cannot be used either.
Challenge Complete!
You've built a complete Live Activity that shows delivery status in real-time on Dynamic Island and the Lock Screen!