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
Podfileentry 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:
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:
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¶
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¶
- Customise detection behaviour in Configuring the SDK
- Understand when and how trips start and stop in Trip Detection
- Learn how crashes are evaluated in Crash Detection
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
applicationIDin 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
Related¶
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 andRoadSaveDelegate/tripDidEnd(_:)is called. Location and activity services are paused. - When set to
true: the SDK performs the same startup guard asRoadSaveSDK/startMonitoring()(fetches settings, enforces offline grace limit). ThrowsRoadSaveError/offlineLimitExceededif 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/shouldAutoDetectTripsat configure time. ThesetAutoTripDetection(_:)method updates it at runtime and matches the legacy 3.xsetAutoTripDetection:behaviour.
Related¶
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 |
Related¶
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.highconfidence 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()isfalse, activity-based trip-start is disabled. Trips can still be started manually. - If
CMPedometer.isStepCountingAvailable()isfalse, step-count trip ending is disabled. The stop-delay timer is used exclusively.
Related¶
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:
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
crashConfirmedandcrashDismissedcallbacks and an immediate pre-servercrashDetected. This was reverted in4.0.0-beta.4to match the legacy contract. If you adopted the beta callbacks, consolidate to a singlecrashDetected(_:)and checkconfidencefor 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.