> ## Documentation Index
> Fetch the complete documentation index at: https://docs.radar.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Building a Live Activity for trip tracking

In this tutorial, we will leverage [Trips](/geofencing/trips) to build a Live Activity for iOS applications to monitor and display the progress of a trip from start to finish, the trip destination, and the arrival ETA on a user’s lock screen and Dynamic Island. If you want to see the full project, you can clone the source code in the section below. This tutorial assumes you have a Radar account and an existing iOS app with the Radar SDK implemented. A detailed implementation overview can be found [here](/sdk/ios).

<img src="https://mintcdn.com/radarlabs/t0CTHCpISjUVFEwr/images/tutorials/live-activity/live-activity-lock-screen.png?fit=max&auto=format&n=t0CTHCpISjUVFEwr&q=85&s=af31f2fc5915a16e549098134a4e7fe0" alt="Live Activity example lock screen" width="1197" height="467" data-path="images/tutorials/live-activity/live-activity-lock-screen.png" />

## Source code

[GitHub Repo](https://github.com/radarlabs/radar-sdk-ios)

## Languages used

* Swift
* SwiftUI

## Features used

* [Trip tracking](/geofencing/trips)
* [Geofences](/geofencing/geofences)

## Steps

<Steps>
  <Step title="Add Widget Extension">
    In Xcode go to  `File -> New -> Target` and search for `widget`:

    <img src="https://mintcdn.com/radarlabs/t0CTHCpISjUVFEwr/images/tutorials/live-activity/live-activity-widget-search.png?fit=max&auto=format&n=t0CTHCpISjUVFEwr&q=85&s=cdf4aebe885a15c31185e7c0db386f54" alt="Live Activity Step 1 setup" width="737" height="528" data-path="images/tutorials/live-activity/live-activity-widget-search.png" />

    Name your Extension `TripActivityExtension` and press `Finish`:

    <img src="https://mintcdn.com/radarlabs/t0CTHCpISjUVFEwr/images/tutorials/live-activity/live-activity-create-widget.png?fit=max&auto=format&n=t0CTHCpISjUVFEwr&q=85&s=8317de39999e5e657f49de2c9c26a792" alt="Live Activity Step 1 finish" width="740" height="524" data-path="images/tutorials/live-activity/live-activity-create-widget.png" />

    This will create and add a new folder and a few files to your project. We will be working with the `TripActivityExtensionLiveActivity` file.

    <img src="https://mintcdn.com/radarlabs/t0CTHCpISjUVFEwr/images/tutorials/live-activity/live-activity-file-structure.png?fit=max&auto=format&n=t0CTHCpISjUVFEwr&q=85&s=30c569b7f5ec71374c8347412f079171" alt="Live Activity Step 1 file structure" width="330" height="146" data-path="images/tutorials/live-activity/live-activity-file-structure.png" />
  </Step>

  <Step title="Add app entitlements">
    Add `Supports Live Activities` and `Supports Live Activities Frequent Updates` to the `info.plist` of your main app:

    <img src="https://mintcdn.com/radarlabs/t0CTHCpISjUVFEwr/images/tutorials/live-activity/live-activity-entitlements.png?fit=max&auto=format&n=t0CTHCpISjUVFEwr&q=85&s=fd53022e55a4153ea5a3040fe1cf4a48" alt="Live Activity Step 1 finish" width="1143" height="213" data-path="images/tutorials/live-activity/live-activity-entitlements.png" />
  </Step>

  <Step title="Create trip activity manager">
    In your app target, create a `TripLiveActivityManager.swift`  and define a `TripLiveActivityManager` class. This is where all of the logic for updating our live activity will live so create a shared instance and add the variables defined below:

    ```swift theme={null}
    import Foundation
    import ActivityKit
    import RadarSDK

    @available(iOS 16.2, *)
    final class TripLiveActivityManager {
        static let shared = TripLiveActivityManager()
        private init() {}

        private var currentActivity: Activity<TripActivityExtensionAttributes>?

        /// Duration (in seconds) to keep the activity visible after ending
        private let dismissalDelay: TimeInterval = 5

        var hasActiveActivity: Bool {
            currentActivity != nil
        }
    }
    ```
  </Step>

  <Step title="Add activity handlers">
    There are three public methods we will use from the `TripLiveActivityManager` to handle starting the live activity, updating it when a user’s location is updated, and ending it when the trip is completed. Let’s add those.

    ```swift theme={null}
    func startActivity(trip: RadarTrip) {
        guard checkActivitiesEnabled() else { return }
        
        // End any existing activity first
        endActivity()
        
        Task {
            let contentState = await buildContentState(from: trip)
            createActivity(contentState: contentState)
        }
    }

    func updateActivity(trip: RadarTrip, statusOverride: String? = nil) {
        guard let activity = currentActivity else {
            print("No active Live Activity to update")
            return
        }
        
        Task {
            let contentState = await buildContentState(from: trip, statusOverride: statusOverride)
            await activity.update(.init(state: contentState, staleDate: nil))
        }
    }

    func endActivity(status: String = "completed") {
        guard let activity = currentActivity else {
            print("No active Live Activity to end")
            return
        }
        
        let finalState = TripActivityExtensionAttributes.ContentState(
            name: "Trip Ended",
            tripId: "",
            status: status,
            etaDuration: nil,
            mode: nil,
            destinationAddress: nil
        )
        Task {
            await activity.end(
                .init(state: finalState, staleDate: nil),
                dismissalPolicy: .after(.now + dismissalDelay)
            )
            currentActivity = nil
            print("Live Activity ended: \(status)")
        }
    }
    ```
  </Step>

  <Step title="Create private methods for TripActivityManager">
    Add the private methods to `TripActivityManager` for creating the activity, building the content state (the data to be displayed in our live activity), and fetching the destination address with the help of `Radar.reverseGeocode()`.

    ```swift theme={null}
    private func checkActivitiesEnabled() -> Bool {
    let authInfo = ActivityAuthorizationInfo()

        guard authInfo.areActivitiesEnabled else {
            print("Live Activities are not enabled - check Settings")
            return false
        }
        return true
    }

    private func createActivity(contentState: TripActivityExtensionAttributes.ContentState) {
        do {
            let activity = try Activity.request(
                attributes: TripActivityExtensionAttributes(),
                content: .init(state: contentState, staleDate: nil),
                pushType: nil
            )
            currentActivity = activity
        } catch {
            print("Error starting Live Activity: \(error.localizedDescription)")
        }
    }

    private func buildContentState(from trip: RadarTrip, statusOverride: String? = nil) async -> TripActivityExtensionAttributes.ContentState {
        let destinationAddress = await fetchDestinationAddress(from: trip)
        
        return TripActivityExtensionAttributes.ContentState(
            name: trip.externalId ?? trip._id,
            tripId: trip._id,
            status: statusOverride ?? trip.status.stringValue,
            etaDuration: Double(trip.etaDuration),
            mode: trip.mode.stringValue,
            destinationAddress: destinationAddress
        )
    }

    private func fetchDestinationAddress(from trip: RadarTrip) async -> String? {
        guard let destinationLocation = trip.destinationLocation else {
            return nil
        }
        
        let location = CLLocation(
            latitude: destinationLocation.coordinate.latitude,
            longitude: destinationLocation.coordinate.longitude
        )
        
        return await withCheckedContinuation { continuation in
            Radar.reverseGeocode(location: location) { status, addresses in
                let address = addresses?.first?.formattedAddress?.truncatedAtFirstComma
                continuation.resume(returning: address)
            }
        }
    }
    ```
  </Step>

  <Step title="Add String Extension">
    Finally, add a `String` extension to truncate the destination address for display purposes.

    ```swift theme={null}
    private extension String {
        var truncatedAtFirstComma: String {
            guard let commaIndex = firstIndex(of: ",") else { return self }
            return String(self[..<commaIndex])
        }
    }
    ```
  </Step>

  <Step title="Handle Live Activity">
    Now that we have our activity manager, we can create the logic for handling our Live Activity. in your app’s `AppDelegate`, add the following function:

    ```swift theme={null}
    @available(iOS 16.2, *)
    private func handleTripLiveActivity(user: RadarUser?) {
        guard let trip = user?.trip else {
            TripLiveActivityManager.shared.endActivity(status: "completed")
            return
        }
        
        let hasActivity = TripLiveActivityManager.shared.hasActiveActivity
        
        switch trip.status {
        case .started, .approaching, .arrived:
            if !hasActivity {
                TripLiveActivityManager.shared.startActivity(trip: trip)
            } else {
                // If trip is "started" but we already have an activity, show as "in_progress"
                let statusOverride = (trip.status == .started) ? "in_progress" : nil
                TripLiveActivityManager.shared.updateActivity(trip: trip, statusOverride: statusOverride)
            }
        case .completed:
            TripLiveActivityManager.shared.endActivity(status: "completed")
        case .canceled:
            TripLiveActivityManager.shared.endActivity(status: "canceled")
        case .expired:
            TripLiveActivityManager.shared.endActivity(status: "expired")
        default:
            break
        }
    }
    ```
  </Step>

  <Step title="Add RadarDelegate functions">
    All that is left for us to do in the app is to hook into the `RadarDelegate` to listen for location updates and Radar events. First, in `applicationDidFinishLaunchingWithOptions` set the delegate after `Radar.initialize()`:

    ```swift theme={null}
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    	//....existing code
        
        Radar.initialize(publishableKey: "prj_test_pk_...", options: radarInitializeOptions )
        Radar.setDelegate(self)
        
        //....existing code
        	
        return true
    }
    ```

    and add the delegate methods below in `AppDelegate`:

    ```swift theme={null}
    func didReceiveEvents(_ events: [RadarEvent], user: RadarUser?) {
        if #available(iOS 16.2, *) {
            for event in events {
                if event.type == .userStoppedTrip {
                    TripLiveActivityManager.shared.endActivity(status: "completed")
                }
            }
        }
    }

    func didUpdateLocation(_ location: CLLocation, user: RadarUser) {
        if #available(iOS 16.2, *) {
            if user.trip != nil {
                handleTripLiveActivity(user: user)
            }
        }
    }
    ```
  </Step>

  <Step title="Build Live Activity views">
    We now have all of the Live Activity and Radar SDK handlers we need to create and update a Live Activity based on Radar Trip data. All we need to do from here is create the SwiftUI views and helper functions in our `TripActivityExtension` to display active trip data. This example includes various assets and design choices you may want to change but for the sake of this tutorial the `TripActivityExtension`  file structure is as follows:

    ```
    TripActivityExtension/
    ├── Assets
    ├── TripActivityExtensionLiveActivity.swift
    ├── TripActivityExtensionBundle.swift
    ├── Views/
    │   ├── LockScreenView.swift
    │   ├── DynamicIslandViews.swift
    │   ├── ProgressBar.swift
    │   └── RadarArrowImage.swift
    ├── Helpers/
    │   ├── TripColors.swift
    │   └── TripFormatters.swift
    └── Extensions/
        └── UIColor+Hex.swift
    ```

    <Info>
      Make sure the `Target Membership` for these files includes your app and the extension
    </Info>

    <img src="https://mintcdn.com/radarlabs/t0CTHCpISjUVFEwr/images/tutorials/live-activity/live-activity-target-membership.png?fit=max&auto=format&n=t0CTHCpISjUVFEwr&q=85&s=7799b562819c33fef1fc44bdbac41733" alt="Live Activity Step 9 finish" width="261" height="146" data-path="images/tutorials/live-activity/live-activity-target-membership.png" />
  </Step>

  <Step title="Create LockScreenView">
    Add `Views/LockScreenView.swift`

    ```swift theme={null}
    import ActivityKit
    import WidgetKit
    import SwiftUI

    @available(iOS 16.2, *)
    struct LockScreenView: View {
        let context: ActivityViewContext<TripActivityExtensionAttributes>

        var body: some View {
            ZStack {
                content
                gradientOverlay
            }
            .activityBackgroundTint(TripColors.background)
            .activitySystemActionForegroundColor(.white)
        }

        // MARK: - Content

        private var content: some View {
            VStack(alignment: .leading, spacing: 0) {
                headerRow
                Spacer()
                statusText
                Spacer()
                progressSection
            }
            .padding(.horizontal, 10)
            .padding(.vertical, 8)
            .padding(.top, 10)
        }

        // MARK: - Header

        private var headerRow: some View {
            HStack(alignment: .center, spacing: 8) {
                RadarArrowImage(variant: .twilight)
                    .frame(width: 20, height: 20)

                Spacer()
            
                Text("\(context.state.destinationAddress ?? "undefined") | \(TripFormatters.formatDuration(context.state.etaDuration))")
                    .font(.system(size: 16, weight: .regular))
                    .foregroundColor(TripColors.twilight)
                    .lineLimit(1)
                    .truncationMode(.head)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing)
            .padding(.trailing, 10)
        }

        // MARK: - Status

        private var statusText: some View {
            let step = TripFormatters.mapStatusToStep(context.state.status)
            return Text(TripFormatters.statusMessage(for: step))
                .font(.system(size: 16, weight: .bold))
                .foregroundColor(TripColors.twilight)
                .multilineTextAlignment(.leading)
                .frame(maxWidth: .infinity, alignment: .leading)
                .padding(.vertical, 8)
                .padding(.bottom, 12)
                .padding(.top, 6)
                .padding(.horizontal, 8)
        }

        // MARK: - Progress

        private var progressSection: some View {
            HStack(alignment: .center, spacing: 4) {
                ProgressBar(currentStep: TripFormatters.mapStatusToStep(context.state.status))
            }
            .padding(.horizontal, 8)
            .padding(.bottom, 10)
        }

        // MARK: - Gradient Overlay

        private var gradientOverlay: some View {
            VStack {
                LinearGradient(
                    gradient: Gradient(stops: [
                        .init(color: TripColors.twilight.opacity(0.2), location: 0.0),
                        .init(color: TripColors.twilight.opacity(0.1), location: 0.5),
                        .init(color: Color.clear, location: 1.0)
                    ]),
                    startPoint: .top,
                    endPoint: .bottom
                )
                .frame(height: 60)
            
                Spacer()
            }
        }
    }
    ```
  </Step>

  <Step title="Create ModeImageView">
    Add `Views/ModeImageView.swift`

    ```swift theme={null}
    import WidgetKit
    import SwiftUI

    struct ModeImageView: View {
        let mode: String?
        let width: CGFloat
        let height: CGFloat
        let topPadding: CGFloat

        init(mode: String?, width: CGFloat = 30, height: CGFloat = 30, topPadding: CGFloat = 4) {
            self.mode = mode
            self.width = width
            self.height = height
            self.topPadding = topPadding
        }

        var body: some View {
            Image(imageName(for: mode))
                .renderingMode(.template)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: width, height: height)
                .padding(.top, topPadding)
                .foregroundColor(.white)
        }

        private func imageName(for mode: String?) -> String {
            switch mode {
            case "car":
                return "radarCarWhite"
            case "bike":
                return "radarBikeWhite"
            default:
                return "radarWalkingWhite"
            }
        }
    }
    ```
  </Step>

  <Step title="Create ProgressBar">
    Add `Views/ProgressBar.swift`

    ```swift theme={null}
    import ActivityKit
    import WidgetKit
    import SwiftUI

    struct ProgressBar: View {
        let currentStep: Int
        let totalSteps: Int = 4
        var isDynamicIsland: Bool = false

        var progressColor: Color {
            isDynamicIsland ? .white : TripColors.twilight
        } 

        var body: some View {
            HStack(spacing: 0) {
                ForEach(1...totalSteps, id: \.self) { step in
                    Circle()
                        .fill(step <= currentStep ? progressColor : Color(UIColor(hexString: "#ACBDC8")).opacity(0.5))
                        .frame(width: step == currentStep ? 30 : 12, height: step == currentStep ? 30 : 12)
                        .overlay(
                            Group {
                                if step == currentStep {
                                    Image(iconForStep(step))
                                        .resizable()
                                        .aspectRatio(contentMode: .fit)
                                        .frame(width: 14, height: 14)
                                }
                            }
                        )

                    if step < totalSteps {
                        if #available(iOS 15.0, *) {
                            Rectangle()
                                .fill(rectangleFill(for: step))
                                .frame(height: 2)
                                .frame(maxWidth: .infinity)
                        } else {
                            Rectangle()
                                .fill(Color(UIColor(hexString: "#ACBDC8")).opacity(0.5))
                                .frame(height: 2)
                                .frame(maxWidth: .infinity)
                        }
                    }
                }
            }
        }

        func iconForStep(_ step: Int) -> String {
            let baseIcon: String
            switch step {
            case 1:
                baseIcon = "radarFlagStart"
            case 2:
                baseIcon = "radarWalking"
            case 3:
                baseIcon = "radarWalking"
            case 4:
                baseIcon = "radarFlagEnd"
            default:
                baseIcon = "radarFlagStar"
            }
            if isDynamicIsland == true {
                return baseIcon + "Blue"
            } else {
                return baseIcon + "White"
            }
        }

        @available(iOS 15.0, *)
        func rectangleFill(for step: Int) -> AnyShapeStyle {
            let incompleteStepColor = Color(UIColor(hexString: "#ACBDC8")).opacity(0.5)
            if step > currentStep - 1 {
                return AnyShapeStyle(incompleteStepColor)
            } else {
                return AnyShapeStyle(progressColor)
            }
        }
    }
    ```
  </Step>

  <Step title="Create RadarArrowImage">
    Add `Views/RadarArrowImage.swift`

    ```swift theme={null}
    import SwiftUI

    @available(iOS 16.2, *)
    struct RadarArrowImage: View {
        enum Variant {
            case twilight, white
            
            var imageName: String {
                switch self {
                case .twilight: return "RadarArrowTwilight"
                case .white: return "RadarArrowWhite"
                }
            }
        }
        
        let variant: Variant
        var size: CGFloat = 20
        
        var body: some View {
            Image(variant.imageName)
                .resizable()
                .scaledToFit()
                .frame(width: size, height: size)
        }
    }
    ```
  </Step>

  <Step title="Create TripColors">
    Add `Helpers/TripColors.swift`

    ```swift theme={null}
    import SwiftUI

    enum TripColors {
        static let twilight = Color(UIColor(hexString: "#2A1688"))
        static let grayAccent = Color(UIColor(hexString: "#ACBDC8"))
        static let background = Color.white
    }
    ```
  </Step>

  <Step title="Create TripFormatters">
    Add `Helpers/TripFormatters.swift`

    ```swift theme={null}
    import Foundation

    enum TripFormatters {
        
        static func formatDuration(_ duration: Double?, compact: Bool = false) -> String {
            guard let duration = duration else { return "Unknown" }
            
            let minutes = Int(duration.rounded())
            
            if minutes < 1 {
                return "Arriving"
            } else if minutes == 1 {
                return compact ? "1 min" : "1 minute"
            } else {
                return compact ? "\(minutes) min" : "\(minutes) minutes"
            }
        }
        
        static func formatDistance(_ meters: Float) -> String {
            let miles = meters * 0.000621371
            if miles < 0.1 {
                return String(format: "%.0f ft", meters * 3.28084)
            } else {
                return String(format: "%.1f mi", miles)
            }
        }
        
        static func mapStatusToStep(_ status: String) -> Int {
            switch status {
            case "started": return 1
            case "in_progress": return 2
            case "approaching": return 3
            case "arrived", "completed", "expired", "canceled": return 4
            default: return 0
            }
        }
        
        static func statusMessage(for step: Int) -> String {
            switch step {
            case 0: return "Your trip status is unknown"
            case 1: return "Your order is confirmed"
            case 2: return "Your trip is in progress"
            case 3: return "Approaching your destination"
            case 4: return "You have arrived at your destination"
            default: return "Your trip status is unknown"
            }
        }
    }
    ```
  </Step>

  <Step title="Add UIColor extension">
    Add `Extensions/UIColor+Hex.swift`

    ```swift theme={null}
    import UIKit

    extension UIColor {
        convenience init(red: Int, green: Int, blue: Int) {
            assert(red >= 0 && red <= 255, "Invalid red component")
            assert(green >= 0 && green <= 255, "Invalid green component")
            assert(blue >= 0 && blue <= 255, "Invalid blue component")
            
            self.init(
                red: CGFloat(red) / 255.0,
                green: CGFloat(green) / 255.0,
                blue: CGFloat(blue) / 255.0,
                alpha: 1.0
            )
        }
        
        convenience init(rgb: Int) {
            self.init(
                red: (rgb >> 16) & 0xFF,
                green: (rgb >> 8) & 0xFF,
                blue: rgb & 0xFF
            )
        }
        
        convenience init(hexString: String) {
            let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
            var int: UInt64 = 0
            Scanner(string: hex).scanHexInt64(&int)
            let a, r, g, b: UInt64
            switch hex.count {
            case 3: // RGB (12-bit)
                (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
            case 6: // RGB (24-bit)
                (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
            case 8: // ARGB (32-bit)
                (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
            default:
                (a, r, g, b) = (255, 0, 1, 52)
            }
            self.init(
                red: Double(r) / 255,
                green: Double(g) / 255,
                blue: Double(b) / 255,
                alpha: Double(a) / 255
            )
        }
    }
    ```
  </Step>

  <Step title="Download Assets">
    The assets used in this example Live Activity can be downloaded [here](https://github.com/radarlabs/radar-sdk-ios/tree/master/Example/TripActivityExtension/Assets.xcassets). Add them to `TripActivityExtension/Assets`.
  </Step>

  <Step title="Update Main View">
    Update `TripActivityExtensionLiveActivity.swift` to use the new views.

    ```swift theme={null}
    import ActivityKit
    import WidgetKit
    import SwiftUI

    // MARK: - Activity Attributes
    struct TripActivityExtensionAttributes: ActivityAttributes {
        public struct ContentState: Codable, Hashable {
            var name: String
            var tripId: String
            var status: String
            var etaDuration: Double?
            var mode: String?
            var destinationAddress: String?
        }
    }

    @available(iOS 16.2, *)
    struct TripActivityExtensionLiveActivity: Widget {
        var body: some WidgetConfiguration {
            ActivityConfiguration(for: TripActivityExtensionAttributes.self) { context in
                LockScreenView(context: context)
            } dynamicIsland: { context in
                DynamicIsland {
                    DynamicIslandExpandedRegion(.leading) {
                        RadarArrowImage(variant: .white, size: 30)
                            .frame(maxHeight: .infinity, alignment: .center)
                            .padding(.leading, 16)
                    }
                    
                    DynamicIslandExpandedRegion(.trailing) {
                        VStack(alignment: .trailing) {
                            Text(TripFormatters.formatDuration(context.state.etaDuration, compact: true))
                                .font(.system(size: 18, weight: .regular))
                                .foregroundColor(.white)
                        }
                        .padding(.trailing, 16)
                    }
                    
                    DynamicIslandExpandedRegion(.bottom) {
                        HStack(alignment: .center, spacing: 4) {
                            ProgressBar(
                                currentStep: TripFormatters.mapStatusToStep(context.state.status),
                                isDynamicIsland: true
                            )
                        }
                        .padding(.horizontal)
                        .padding(.top, 8)
                    }
                } compactLeading: {
                    ModeImageView(mode: context.state.mode, width: 18, height: 18, topPadding: 0)
                } compactTrailing: {
                    Text(TripFormatters.formatDuration(context.state.etaDuration, compact: true))
                        .foregroundColor(.white)
                } minimal: {
                    ModeImageView(mode: context.state.mode, width: 18, height: 18, topPadding: 0)
                }
            }
        }
    }

    // MARK: - Preview Data
    @available(iOS 16.2, *)
    extension TripActivityExtensionAttributes {
        fileprivate static var preview: TripActivityExtensionAttributes {
            TripActivityExtensionAttributes()
        }
    }

    @available(iOS 16.2, *)
    extension TripActivityExtensionAttributes.ContentState {
        fileprivate static var started: TripActivityExtensionAttributes.ContentState {
            TripActivityExtensionAttributes.ContentState(
                name: "My Trip",
                tripId: "trip_123",
                status: "started",
                etaDuration: 15.5,
                mode: "car",
                destinationAddress: "123 Main St, San Francisco, CA"
            )
        }
        
        fileprivate static var approaching: TripActivityExtensionAttributes.ContentState {
            TripActivityExtensionAttributes.ContentState(
                name: "My Trip",
                tripId: "trip_123",
                status: "approaching",
                etaDuration: 2.0,
                mode: "car",
                destinationAddress: "123 Main St, San Francisco, CA"
            )
        }
    }

    @available(iOS 18.0, *)
    #Preview("Notification", as: .content, using: TripActivityExtensionAttributes.preview) {
        TripActivityExtensionLiveActivity()
    } contentStates: {
        TripActivityExtensionAttributes.ContentState.started
        TripActivityExtensionAttributes.ContentState.approaching
    }
    ```

    We now have a fully functional Live Activity that updates based on Radar Trips data on a user’s lock screen and Dynamic Island!
  </Step>

  <Step title="Create a Geofence">
    With our Live Activity set up, the last thing we need to do is start a trip. If you don’t have any geofences in your Radar project, you will need to add one. For this example I have created the geofence with the identifier `trip12345`, shown below:

    <img src="https://mintcdn.com/radarlabs/t0CTHCpISjUVFEwr/images/tutorials/live-activity/live-activity-geofence.png?fit=max&auto=format&n=t0CTHCpISjUVFEwr&q=85&s=a265e7d7ad80cbe26a6cc2f8f514c510" alt="Live Activity Step 19 geofence" width="1959" height="619" data-path="images/tutorials/live-activity/live-activity-geofence.png" />
  </Step>

  <Step title="Start a trip">
    In order to test your new Live Activity, call `Radar.startTrip` with a `destinationGeofenceTag` and `destinationGeofenceExternalId` that match the Geofence in your Radar project. You can also include any additional `RadarTripOptions` you need. See the example below for reference:

    ```swift theme={null}
    let uniqueTripId = "trip_\(Int(Date().timeIntervalSince1970))"
    let tripOptions = RadarTripOptions(externalId: uniqueTripId, destinationGeofenceTag: "trip_activity", destinationGeofenceExternalId: "trip12345", scheduledArrivalAt: nil, startTracking: false)

    tripOptions.mode = .car
    tripOptions.approachingThreshold = 5

    let trackingOptions = RadarTrackingOptions.presetContinuous

    Radar.startTrip(options: tripOptions, trackingOptions: trackingOptions)         
    ```
  </Step>
</Steps>

### Conclusion

Congratulations on finishing the tutorial! Once you have started a trip, you should see your Live Activity on your lock screen and in your dynamic island.

**Lock Screen**

<img src="https://mintcdn.com/radarlabs/t0CTHCpISjUVFEwr/images/tutorials/live-activity/live-activity-lock-screen.png?fit=max&auto=format&n=t0CTHCpISjUVFEwr&q=85&s=af31f2fc5915a16e549098134a4e7fe0" alt="Live Activity example lock screen" width="1197" height="467" data-path="images/tutorials/live-activity/live-activity-lock-screen.png" />

**Dynamic Island**

<img src="https://mintcdn.com/radarlabs/t0CTHCpISjUVFEwr/images/tutorials/live-activity/live-activity-island.jpg?fit=max&auto=format&n=t0CTHCpISjUVFEwr&q=85&s=b060daa876121da9012eadf47904a4ba" alt="Live Activity example dynamic island" width="1206" height="210" data-path="images/tutorials/live-activity/live-activity-island.jpg" />

**Expanded Dynamic Island**

<img src="https://mintcdn.com/radarlabs/t0CTHCpISjUVFEwr/images/tutorials/live-activity/live-activity-expanded-island.png?fit=max&auto=format&n=t0CTHCpISjUVFEwr&q=85&s=237ffb881addc75170cfc7f55055876c" alt="Live Activity example dynamic island expanded" width="1206" height="332" data-path="images/tutorials/live-activity/live-activity-expanded-island.png" />

### Support

Have questions? Contact us at [radar.com/support](https://radar.com/support).
