🌐 KO

πŸƒ Delivery Tracking Live Activity

⭐ Difficulty: ⭐⭐⭐ ⏱️ Est. Time: 2-3h πŸ“‚ App Frameworks

Build a Live Activity using ActivityKit to track delivery status on the Lock Screen and Dynamic Island in real-time.

πŸ“… 2025.02 ⏱ Estimated 2 Hours πŸ“± iOS 16.1+ / Xcode 15+ 🎯 Intermediate

πŸ“– 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 Use Cases for Live Activity

βœ… Good Examples
Delivery/taxi tracking, sports scores, timers, music playback, flight info

❌ Bad Examples
Weather (β†’ Widget), Calendar reminders (β†’ Notification), Stocks (β†’ Widget)

πŸ—οΈ Define ActivityAttributes

DeliveryAttributes.swift Swift
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.

DeliveryLiveActivity.swift Swift
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

LockScreenView.swift Swift
struct LockScreenView: View {
    let context: ActivityViewContext<DeliveryAttributes>

    var body: some View {
        HStack(spacing: 12) {
            // Status μ•„μ΄μ½˜
            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

ActivityManager.swift Swift
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. update state
    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:

Info.plist XML
<key>NSSupportsLiveActivities</key>
<true/>

πŸ”” Push Notification Integration (Optional)

To update remotely from a server, use Push Tokens:

PushToken.swift Swift
// Push Token Fetch
for await pushToken in Activity<DeliveryAttributes>.pushToStartTokenUpdates {
    // Server둜 Push Token 전솑
    await sendTokenToServer(pushToken)
}

⚠️ Important Notes

🚨 Live Activity Limitations

⏱️ 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!

πŸ“¦ Learning Resources

πŸ“š
DocC Tutorial
Try it in Xcode
πŸ’»
GitHub Project
Complete Source Code
🍎
Apple HIG Docs
Live Activities Guide

πŸ“Ž Apple Official Resources

πŸ“˜ Documentation πŸ’» Sample Code 🎬 WWDC Sessions