Skip to content

Getting Started

Add RoadSaveKit to your iOS app and start detecting crashes and trips in minutes.

Overview

RoadSaveKit gives your app crash detection, trip detection, and activity recognition powered by the RoadSave platform. This article walks you from installation to your first crash-detection callback in four steps.

Requirements

Requirement Minimum
iOS deployment target 17.0
Xcode 16.0
Swift 5.9
Distribution Swift Package Manager only (see note below)

CocoaPods is not supported. The legacy 3.x SDK was only exposed via CocoaPods as a transitive dev dependency for AFNetworking; version 4.0 distributes exclusively through Swift Package Manager. Consumers migrating from 3.x should remove their Podfile entry and follow Step 1 below.

Step 1 — Add the Package (Swift Package Manager)

In Xcode, choose File → Add Package Dependencies and enter the repository URL:

https://github.com/Dynamus-Technologies/roadsave-ios-sdk

Select a version starting from 4.0.0 and add RoadSaveKit to your app target.

Alternatively, add it to Package.swift:

dependencies: [
    .package(url: "https://github.com/Dynamus-Technologies/roadsave-ios-sdk", from: "4.0.0")
],
targets: [
    .target(name: "MyApp", dependencies: ["RoadSaveKit"])
]

Step 2 — Add Required Permissions

RoadSaveKit uses location and motion hardware. Add the following keys to your Info.plist:

Key Purpose
NSLocationAlwaysAndWhenInUseUsageDescription Required for background trip and crash detection
NSMotionUsageDescription Required for activity recognition and crash detection

Important: Always-on location permission is mandatory. Trip and crash detection cannot function with "When In Use" permission alone.

Background Modes entitlement

The SDK monitors location while the app is in the background. Add location to UIBackgroundModes in your Info.plist:

<key>UIBackgroundModes</key>
<array>
    <string>location</string>
</array>

Or via Xcode's Signing & Capabilities tab: add the Background Modes capability and enable Location updates.

Note: The SDK cannot declare background modes on your app's behalf — this entitlement must be added to the host app target directly. Without it, the system will suspend location updates when the app backgrounds, and trip detection will not function correctly.

Step 3 — Configure at Startup

Call RoadSaveSDK/configure(with:) once, before any other SDK calls. Place this in application(_:didFinishLaunchingWithOptions:) or your SwiftUI App.init:

import RoadSaveKit

func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
    Task {
        let config = RoadSaveConfiguration(
            applicationID: "your-app-id",
            clientUserID: currentUser.id
        )
        do {
            try await RoadSaveSDK.shared.configure(with: config)
        } catch {
            print("SDK configuration failed: \(error)")
        }
    }
    return true
}

Step 4 — Set a Delegate and Start Monitoring

await RoadSaveSDK.shared.setDelegate(self)
try await RoadSaveSDK.shared.startMonitoring()

Implement RoadSaveDelegate to receive events:

extension AppDelegate: RoadSaveDelegate {
    func tripDidStart(_ tripInfo: TripStartInfo) {
        print("Trip started: \(tripInfo.tripID)")
    }

    func tripDidEnd(_ tripInfo: TripInfo) {
        print("Trip ended. Distance: \(tripInfo.distanceMetres)m")
    }

    /// Fired once per crash, only after the server evaluation round-trip completes.
    /// `crashInfo.confidence` reflects the server's verdict. If the server returns
    /// "None", this callback is suppressed entirely.
    func crashDetected(_ crashInfo: CrashInfo) {
        if crashInfo.confidence == .unknown {
            // Server unreachable — show a softer "possible crash" message.
            presentCrashAlert(for: crashInfo)
        } else {
            // Server-confirmed crash.
            presentCrashAlert(for: crashInfo)
            uploadCrashReport(crashInfo)
        }
    }
}

Next Steps


Configuring the SDK

Customise SDK behaviour to match your product requirements.

Overview

All SDK behaviour is controlled through RoadSaveConfiguration. You construct one configuration object at startup and pass it to RoadSaveSDK/configure(with:).

Required Parameters

Every configuration requires two fields:

let config = RoadSaveConfiguration(
    applicationID: "your-app-id",   // Issued by RoadSave
    clientUserID:  "user-123"       // Your app's user identifier
)

Security: Never hardcode applicationID in source code. Read it from a server-fetched config, a build environment variable, or a secure keychain entry.

Detection Mode

CrashDetectionMode lets you disable crash detection without stopping trip detection:

// Crash detection only — no trip tracking.
config.detectionMode = .crashOnly

// Trip tracking only — crashes are not evaluated.
config.detectionMode = .tripOnly

// Both active (default).
config.detectionMode = .all

Trip Detection

Understand how the SDK automatically detects the start and end of driving trips.

Overview

The SDK monitors device sensors and signals continuously in the background. A trip begins automatically when the SDK determines the user is driving, and ends when the user has stopped and is no longer in a vehicle. No manual intervention is required — the SDK handles start and stop detection entirely on its own.

How Trips Are Detected

Trip start and stop decisions are made by combining multiple sensor signals. The SDK requires consistent evidence across those signals before starting or ending a trip, which reduces false positives from activities like cycling, being a passenger on public transport, or briefly stopping at traffic lights.

Receiving Trip Events

Implement RoadSaveDelegate to receive callbacks:

func tripDidStart(_ tripInfo: TripStartInfo) {
    print("Trip \(tripInfo.tripID) started at \(tripInfo.startDate)")
}

func tripDidEnd(_ tripInfo: TripInfo) {
    print("Trip \(tripInfo.tripID) covered \(tripInfo.distanceMetres)m")
}

TripStartInfo provides the trip's unique ID and start timestamp. TripInfo adds the end timestamp, total distance, and average speed.

Runtime Auto-Detection Control

You can enable or disable automatic trip detection at any time after the SDK is configured, without dismantling and reinitialising it. Use RoadSaveSDK/setAutoTripDetection(_:):

// Pause trip detection mid-session.
try await RoadSaveSDK.shared.setAutoTripDetection(false)

// Re-enable later — the SDK re-fetches settings and resumes monitoring.
try await RoadSaveSDK.shared.setAutoTripDetection(true)

Behaviour:

  • When set to false: any trip currently in progress is finalised and RoadSaveDelegate/tripDidEnd(_:) is called. Location and activity services are paused.
  • When set to true: the SDK performs the same startup guard as RoadSaveSDK/startMonitoring() (fetches settings, enforces offline grace limit). Throws RoadSaveError/offlineLimitExceeded if the limit is reached.
  • Crash detection is unaffected by this toggle — crashes can still be detected while trip detection is paused.

The initial value of this setting is determined by RoadSaveConfiguration/shouldAutoDetectTrips at configure time. The setAutoTripDetection(_:) method updates it at runtime and matches the legacy 3.x setAutoTripDetection: behaviour.


Crash Detection

Learn how the SDK detects crashes and reports the outcome to your delegate.

Overview

Crash detection runs continuously in the background whenever monitoring is active. The SDK monitors device sensors and, when a suspected crash event is detected, submits it to the RoadSave platform for server-side confirmation. The outcome is delivered to your delegate once the evaluation is complete.

Delegate Callback

Your RoadSaveDelegate receives a single crash callback, fired only after server evaluation completes:

func crashDetected(_ crashInfo: CrashInfo) {
    switch crashInfo.confidence {
    case .high:
        // Strong evidence of a crash — notify emergency contacts, upload report, etc.
    case .medium:
        // Moderate evidence — prompt the user to confirm.
    case .low:
        // Weak evidence — use discretion.
    case .unknown:
        // The server could not be reached or the event could not be confirmed.
        // Show a softer "possible crash — unconfirmed" message.
    }
}

If the server determines no crash occurred, crashDetected is not called at all.

CrashConfidence

CrashConfidence has four possible values:

Value Meaning
.high Strong evidence of a crash confirmed by server evaluation
.medium Moderate evidence — server confirmed with reduced certainty
.low Weak evidence — event detected but confidence is marginal
.unknown Server unreachable or event could not be confirmed

When the confidence is .unknown, the SDK still fires crashDetected because an event was detected that could not be dismissed. Host apps should surface a softer message to the user (e.g. "We detected something that might be a crash, but couldn't confirm it") rather than a high-confidence crash alert.

CrashInfo

CrashInfo carries the full context of the event:

Property Type Description
crashID String Unique identifier for this crash event
confidence CrashConfidence .low, .medium, .high, or .unknown
detectedAt Date When the event was detected
latitude Double GPS latitude at the time of impact
longitude Double GPS longitude at the time of impact

Activity Recognition

Understand how the SDK classifies motion activities and uses them to gate trip and crash detection.

Overview

RoadSaveKit uses Core Motion's CMMotionActivityManager to classify the user's current physical activity — stationary, walking, running, cycling, or automotive — and feeds this signal into trip detection logic. Activity classification is a background process that begins automatically when RoadSaveSDK/startMonitoring() is called.

Activity Types

The SDK maps Core Motion activity types to its own ActivityType enum:

ActivityType Core Motion equivalent Trip detection meaning
.automotive CMMotionActivity.automotive Required for auto-trip start (high confidence)
.walking CMMotionActivity.walking May trigger trip-end via step threshold
.running CMMotionActivity.running May trigger trip-end via step threshold
.cycling CMMotionActivity.cycling Neither starts nor stops a trip
.stationary CMMotionActivity.stationary Contributes to stop-delay evaluation
.unknown CMMotionActivity.unknown Ignored

Confidence Levels

Core Motion reports a confidence value alongside each activity classification:

  • .high — activity classification is reliable. Only .high confidence automotive activity can start a trip.
  • .medium — activity is likely but not certain. Used to maintain a trip already in progress.
  • .low — activity classification is unreliable. Ignored for trip-start decisions.

Step-Count Trip Ending

When a trip is active and the user exits the vehicle on foot, the pedometer provides a faster and more reliable stop signal than GPS alone. The SDK monitors step count using CMPedometer. When cumulative steps exceed a configurable threshold since the last GPS movement, the trip is ended immediately without waiting for the stop-delay timer.

This prevents the common false-positive scenario where a driver parks in a multi-story car park and the GPS speed drops to zero but the activity classifier still reports .automotive because the accelerometer is dampened.

Hardware Availability

CMMotionActivityManager and CMPedometer require specific hardware that may not be present on all devices. The SDK checks availability at startup and falls back gracefully:

  • If CMMotionActivityManager.isActivityAvailable() is false, activity-based trip-start is disabled. Trips can still be started manually.
  • If CMPedometer.isStepCountingAvailable() is false, step-count trip ending is disabled. The stop-delay timer is used exclusively.

Migrating from SDK 3.x

Update your integration from the Objective-C SDK 3.x to Swift SDK 4.0.

Overview

Version 4.0 is a complete rewrite in Swift and introduces the RoadSaveKit module name, replacing the previous CrashDetechKit module shipped in 3.x. This reflects the company's trading name, RoadSave (CrashDetech (Pty) Ltd t/a RoadSave).

Module and Import Rename (3.x → 4.0)

Update the import statement in every file that used the SDK:

// Before (3.x)
import CrashDetechKit

// After (4.0)
import RoadSaveKit

Four public types were also renamed to match the new brand prefix:

3.x type 4.0 type
CrashDetechSDK RoadSaveSDK
CrashDetechConfiguration RoadSaveConfiguration
CrashDetechDelegate RoadSaveDelegate
CrashDetechError RoadSaveError

All other public types (CrashInfo, TripInfo, TripStartInfo, CrashConfidence, CrashDetectionMode, FalsePositiveReason) are unchanged — they describe features, not the brand.

Additional Breaking Changes in 4.0 The public API surface is deliberately similar to 3.x but there are a number of breaking changes that require updates to your integration code. This article lists every change that affects consumers.

Entry Point

3.x

[[RoadSaveSDK sharedInstance] configureWithApplicationID:@"app-id" clientUserID:@"user-123"];
[[RoadSaveSDK sharedInstance] startMonitoring];

4.0

let config = RoadSaveConfiguration(applicationID: "app-id", clientUserID: "user-123")
try await RoadSaveSDK.shared.configure(with: config)
try await RoadSaveSDK.shared.startMonitoring()

What changed: - The initialiser is now async throws. Call it inside a Task from a synchronous context. - Configuration is a value type (RoadSaveConfiguration) instead of individual parameters.

Delegate

3.x

- (void)crashDetected:(NSDictionary *)crashData;
- (void)tripDidStart;
- (void)tripDidEnd:(NSDictionary *)tripData;

4.0

public protocol RoadSaveDelegate: AnyObject {
    func crashDetected(_ crashInfo: CrashInfo)
    func tripDidStart(_ tripInfo: TripStartInfo)
    func tripDidEnd(_ tripInfo: TripInfo)
}

What changed: - Callbacks now receive typed Swift structs instead of NSDictionary. - crashDetected(_:) now carries the server-evaluated CrashConfidence — it fires only after the server round-trip completes (or retries exhaust), never before. This matches the legacy 3.0.8 [id<CrashDetechDelegate> crashDetected:] timing exactly. Check crashInfo.confidence to distinguish high/medium/low/unknown outcomes. If the server returns "None", the callback is suppressed entirely. - tripDidStart now receives TripStartInfo. - tripDidEnd now receives TripInfo (with distance, duration, average speed).

4.0.0-beta.1 through beta.3: These pre-releases shipped a tri-state model with separate crashConfirmed and crashDismissed callbacks and an immediate pre-server crashDetected. This was reverted in 4.0.0-beta.4 to match the legacy contract. If you adopted the beta callbacks, consolidate to a single crashDetected(_:) and check confidence for the outcome.

Crash Data

3.x NSDictionary key 4.0 CrashInfo property
"crashID" crashInfo.crashID
"timestamp" crashInfo.timestamp
"latitude", "longitude" crashInfo.location?.coordinate
"confidence" crashInfo.confidence (typed CrashConfidence)

Trip Data

3.x NSDictionary key 4.0 TripInfo property
"tripID" tripInfo.tripID
"startDate" tripInfo.startDate
"endDate" tripInfo.endDate
"distanceMetres" tripInfo.distanceMetres

Removed APIs

The following 3.x APIs have no 4.0 equivalent and have been removed:

Removed Reason
startManualTrip Manual trip control is no longer exposed publicly. Set shouldAutoDetectTrips: true in RoadSaveConfiguration.
stopManualTrip See startManualTrip above.
raiseTestCrash() Use RoadSaveSDK/raiseTestCrash() (restored in 4.0.0-beta.2)
CrashDetectionMode.OFF Use CrashDetectionMode/disabled instead
enableLogging: SDK logging is controlled via OSLog subsystem co.za.roadsave.RoadSaveKit

Changed APIs in 4.0

3.x 4.0 Notes
setAutoTripDetection(Bool) RoadSaveSDK/setAutoTripDetection(_:) Now async throws; same runtime semantics

Installation

The SDK is now distributed as a Swift Package. Remove your CocoaPods or manual framework integration and follow the Swift Package Manager instructions in Getting Started.

Minimum Deployment Target

4.0 requires iOS 17.0. Builds targeting iOS 14–16 must remain on 3.x.