Section 10/141 menit

10. Live Activities dan Dynamic Island

10. Live Activities dan Dynamic Island

Live Activities (iOS 16.1+) adalah widget yang bisa diupdate secara real-time dan ditampilkan di Lock Screen serta Dynamic Island.

Definisikan ActivityAttributes

swift
import ActivityKit

struct DeliveryAttributes: ActivityAttributes {
    // ContentState: data yang BISA berubah selama aktivitas berlangsung
    struct ContentState: Codable, Hashable {
        var currentStep: DeliveryStep
        var estimatedMinutes: Int
        var courierName: String
    }

    // Properties di luar ContentState: data yang TIDAK berubah
    var orderId: String
    var restaurantName: String
    var itemsSummary: String
}

enum DeliveryStep: String, Codable, Hashable {
    case confirmed = "Pesanan Dikonfirmasi"
    case preparing = "Sedang Dimasak"
    case onTheWay = "Dalam Perjalanan"
    case nearYou = "Hampir Tiba"
    case delivered = "Terkirim"

    var iconName: String {
        switch self {
        case .confirmed: return "checkmark.circle"
        case .preparing: return "flame"
        case .onTheWay: return "bicycle"
        case .nearYou: return "location.fill"
        case .delivered: return "house.fill"
        }
    }
}

Tampilan Live Activity

swift
struct DeliveryLiveActivityView: View {
    let context: ActivityViewContext<DeliveryAttributes>

    var body: some View {
        HStack(spacing: 12) {
            Image(systemName: context.state.currentStep.iconName)
                .font(.title2)
                .foregroundStyle(.orange)

            VStack(alignment: .leading, spacing: 2) {
                Text(context.state.currentStep.rawValue)
                    .font(.headline)
                Text(context.attributes.restaurantName)
                    .font(.caption)
                    .foregroundStyle(.secondary)
            }

            Spacer()

            VStack(alignment: .trailing) {
                Text("\(context.state.estimatedMinutes) mnt")
                    .font(.headline.bold())
                Text("Estimasi")
                    .font(.caption2)
                    .foregroundStyle(.secondary)
            }
        }
        .padding()
        .activityBackgroundTint(.black.opacity(0.8))
    }
}

// Dynamic Island configuration
struct DeliveryWidget: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: DeliveryAttributes.self) { context in
            // Lock Screen / Notification-style
            DeliveryLiveActivityView(context: context)
        } dynamicIsland: { context in
            DynamicIsland {
                // Expanded — saat Dynamic Island di-expand
                DynamicIslandExpandedRegion(.leading) {
                    Image(systemName: context.state.currentStep.iconName)
                        .font(.title)
                        .foregroundStyle(.orange)
                }
                DynamicIslandExpandedRegion(.trailing) {
                    Text("\(context.state.estimatedMinutes) mnt")
                        .font(.title2.bold())
                }
                DynamicIslandExpandedRegion(.bottom) {
                    Text(context.state.currentStep.rawValue)
                        .font(.headline)
                }
            } compactLeading: {
                // Compact — sisi kiri Dynamic Island saat collapsed
                Image(systemName: context.state.currentStep.iconName)
                    .foregroundStyle(.orange)
            } compactTrailing: {
                // Compact — sisi kanan
                Text("\(context.state.estimatedMinutes)m")
                    .font(.caption.bold())
            } minimal: {
                // Paling kecil, saat ada banyak aktivitas aktif
                Image(systemName: context.state.currentStep.iconName)
            }
        }
    }
}

Mengelola Live Activity dari App

swift
import ActivityKit

class DeliveryTracker {
    private var activity: Activity<DeliveryAttributes>?

    func startTracking(order: Order) throws {
        guard ActivityAuthorizationInfo().areActivitiesEnabled else {
            throw DeliveryError.liveActivitiesNotEnabled
        }

        let attributes = DeliveryAttributes(
            orderId: order.id,
            restaurantName: order.restaurantName,
            itemsSummary: order.itemsSummary
        )
        let initialState = DeliveryAttributes.ContentState(
            currentStep: .confirmed,
            estimatedMinutes: 35,
            courierName: "Belum ditugaskan"
        )

        activity = try Activity<DeliveryAttributes>.request(
            attributes: attributes,
            contentState: initialState,
            pushType: .token  // Gunakan .token jika update via APNs push
        )
    }

    func updateStatus(step: DeliveryStep, etaMinutes: Int, courierName: String) async {
        let newState = DeliveryAttributes.ContentState(
            currentStep: step,
            estimatedMinutes: etaMinutes,
            courierName: courierName
        )
        await activity?.update(using: newState)
    }

    func endActivity(delivered: Bool) async {
        let finalState = DeliveryAttributes.ContentState(
            currentStep: .delivered,
            estimatedMinutes: 0,
            courierName: activity?.contentState.courierName ?? ""
        )
        // Tampilkan state akhir, lalu hapus setelah beberapa detik
        await activity?.end(using: finalState, dismissalPolicy: .after(.now + 5))
    }
}