🏃 배달 추적 Live Activity
만들기
ActivityKit을 활용해 배달 현황을 잠금 화면과 Dynamic Island에서 실시간으로 확인할 수 있는 Live Activity를 구현합니다.
📖 Live Activity란?
Live Activity는 진행 중인 작업의 실시간 상태를 잠금 화면과 Dynamic Island에 표시합니다. 위젯과 달리 시간 제한(최대 8시간)이 있고, 실시간 업데이트가 가능합니다.
✅ 좋은 사례
배달/택시 추적, 스포츠 경기, 타이머, 음악 재생, 항공편 정보
❌ 나쁜 사례
날씨(→ Widget), 캘린더 알림(→ Notification), 주가(→ Widget)
🏗️ 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 레이아웃
Dynamic Island는 3가지 상태가 있습니다: Compact, Minimal, 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") } } } }
📱 LockScreen View 구현
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 생명주기
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 설정
Live Activity를 사용하려면 Info.plist에 권한을 추가해야 합니다:
<key>NSSupportsLiveActivities</key> <true/>
🔔 Push Notification 연동 (선택)
서버에서 원격으로 업데이트하려면 Push Token을 사용합니다:
// Push Token 가져오기 for await pushToken in Activity<DeliveryAttributes>.pushToStartTokenUpdates { // 서버로 Push Token 전송 await sendTokenToServer(pushToken) }
⚠️ 주의사항
⏱️ 시간 제한
최대 8시간 동안만 표시됩니다. 그 이후에는 자동으로 제거됩니다.
🔋 배터리 영향
너무 자주 업데이트하면 배터리 소모가 큽니다. 최소 5초 간격을 권장합니다.
📱 기기 제한
Dynamic Island는 iPhone 14 Pro 이상에서만 표시됩니다. 다른 기기에서는 잠금 화면에만 표시됩니다.
🎨 UI 제약
탭 제스처, 애니메이션, 비디오는 지원되지 않습니다. Button, Toggle, TextField도 사용할 수 없습니다.
챌린지 완료!
Dynamic Island와 잠금 화면에서 배달 현황을 실시간으로 확인할 수 있는 Live Activity를 완성했습니다!