Install SDK on iOS

Add HyperTrack SDK to your project

Swift Package Manager

Select File > Swift Packages > Add Package Dependency.... Enter this URL https://github.com/hypertrack/sdk-ios in the search field to install the dependency.

Swift Package Manager is supported in Xcode 12 and up.

CocoaPods

If you don't have CocoaPods installed, you can install it here.

Using command line run pod init in your project directory to create a Podfile. Put the following code (changing target placeholder to your target name) in the Podfile:

platform :ios, '11.0'
inhibit_all_warnings!
target 'YourApp' do
use_frameworks!
pod 'HyperTrack', '4.6.0'
end
note

We constantly work on making our SDKs better, so make sure you have the latest version of it. You might take a look of its changelog here.

Run pod install. CocoaPods will build the dependencies and create a workspace (.xcworkspace) for you.

If your project uses Objective-C only, you need to configure SWIFT_VERSION in your project's Build Settings. Alternatively, you can create an empty Swift file, and Xcode will create this setting for you. Set Swift version to 5. Additionally change pod 'HyperTrack', '4.6.0' to pod 'HyperTrack/Objective-C', '4.6.0' in your Podfile.

Enable background location updates

Enable Background Modes in your project target's Capabilities tab. Choose "Location updates".

Capabilities tab in Xcode

Handle location and motion permissions

Set the following purpose strings in the Info.plist file:

Always authorization location

HyperTrack SDK requires "Always" permissions to reliably track user's location. Be advised, purpose strings are mandatory.

Your app needs to make sure that it has location and motion permissions for location tracking to work. See this F.A.Q. page for details on permissions best practices.

Initialize the SDK

Get your publishable key from the Setup page.

Put the initialization call inside your AppDelegate's application:didFinishLaunchingWithOptions: method:

Swift
let publishableKey = HyperTrack.PublishableKey("PASTE_YOUR_PUBLISHABLE_KEY_HERE")!
switch HyperTrack.makeSDK(publishableKey: publishableKey) {
case let .success(hyperTrack):
// Use `hyperTrack` instance
case let .failure(fatalError):
// Handle errors, for example using switch
}
Objective-C

Import the SDK:

@import HyperTrack;

Initialize the SDK.

NSString *publishableKey = @"PASTE_YOUR_PUBLISHABLE_KEY_HERE";
HTResult *result = [HTSDK makeSDKWithPublishableKey:publishableKey];
if (result.hyperTrack != nil) {
// Use `hyperTrack` instance from `result.hyperTrack`
} else {
// Handle errors, for example using switch:
switch ([result.error code]) {
case HTFatalErrorProductionLocationServicesUnavalible:
case HTFatalErrorProductionMotionActivityServicesUnavalible:
// Handle a case where device is fully untrackable (either iPhone 5 or lower
// or not an iPhone
break;
case HTFatalErrorProductionMotionActivityPermissionsDenied:
// Handle motion permissions denied error. Enabling permissions will
// restart the app
default:
// Other errors should only happen during development
break;
}
}

NSNotifications

Restorable and Unrestorable error notifications are called if the SDK encounters an error that prevents it from tracking. SDK can recover in runtime from Restorable errors if the error reason is resolved. Errors include:

  • Initialization errors, like denied Location or Motion permissions (RestorableError.locationPermissionsDenied)
  • Authorization errors from the server. If the trial period ends and there is no credit card tied to the account, this is the error that will be called (RestorableError.trialEnded)
  • Incorrectly typed Publishable Key (UnrestorableError.invalidPublishableKey)
Swift
If you want to handle errors using the same selector:
NotificationCenter.default.addObserver(
self,
selector: #selector(trackingError(notification:)),
name: HyperTrack.didEncounterUnrestorableErrorNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(trackingError(notification:)),
name: HyperTrack.didEncounterRestorableErrorNotification,
object: nil
)
...
@objc func trackingError(notification: Notification) {
if let trackingError = notification.hyperTrackTrackingError() {
// Handle TrackingError, which is an enum of Restorable or Unrestorable error
}
}
If you want to handle errors separately, or handle only Restorable or only Unrestorable errors:
NotificationCenter.default.addObserver(
self,
selector: #selector(unrestorableError(notification:)),
name: HyperTrack.didEncounterUnrestorableErrorNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(restorableError(notification:)),
name: HyperTrack.didEncounterRestorableErrorNotification,
object: nil
)
...
@objc func restorableError(notification: Notification) {
if let restorableError = notification.hyperTrackRestorableError() {
// Handle RestorableError
}
}
@objc func unrestorableError(notification: Notification) {
if let unrestorableError = notification.hyperTrackUnrestorableError() {
// Handle UnrestorableError
}
}
Objective-C
If you want to handle errors using the same selector:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(hyperTrackEncounteredTrackingError:)
name:HTSDK.didEncounterRestorableErrorNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(hyperTrackEncounteredTrackingError:)
name:HTSDK.didEncounterUnrestorableErrorNotification
object:nil];
...
- (void)hyperTrackEncounteredTrackingError:(NSNotification *)notification {
// Use tracking error helper
NSError *error = [notification hyperTrackTrackingError];
if (error != nil) {
if ([[error domain] isEqualToString:NSError.HTRestorableErrorDomain]) {
// Handle restorable error
} else if ([[error domain] isEqualToString:NSError.HTUnrestorableErrorDomain]) {
// Handle unrestorable error
}
}
}
If you want to handle errors separately, or handle only Restorable or only Unrestorable errors:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(hyperTrackEncounteredRestorableError:)
name:HTSDK.didEncounterRestorableErrorNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(hyperTrackEncounteredUnrestorableError:)
name:HTSDK.didEncounterUnrestorableErrorNotification
object:nil];
...
- (void)hyperTrackEncounteredRestorableError:(NSNotification *)notification {
NSError *restorableError = [notification hyperTrackRestorableError]);
// Handle RestorableError
}
- (void)hyperTrackEncounteredUnrestorableError:(NSNotification *)notification {
NSError *unrestorableError = [notification hyperTrackUnrestorableError]);
// Handle UnrestorableError
}

You can also observe when SDK starts and stops tracking and update the UI:

Swift
NotificationCenter.default.addObserver(
self,
selector: #selector(self.trackingStarted),
name: HyperTrack.startedTrackingNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(self.trackingStopped),
name: HyperTrack.stoppedTrackingNotification,
object: nil
)
Objective-C
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(trackingStarted)
name:HTSDK.startedTrackingNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(trackingStopped)
name:HTSDK.stoppedTrackingNotification
object:nil];

Enable remote notifications

The SDK has a bi-directional communication model with the server. This enables the SDK to run on a variable frequency model, which balances the fine trade-off between low latency tracking and battery efficiency, and improves robustness. For this purpose, the iOS SDK uses APNs silent remote notifications.

note

This guide assumes you have configured APNs in your application. If you haven't, read the iOS documentation on APNs.

Configure APNs on the dashboard

Log into the HyperTrack dashboard, and open the setup page. Upload your Auth Key (file in the format AuthKey_KEYID.p8) and fill in your Team ID.

This key will only be used to send silent push notifications to your apps.

Enable remote notifications in the app

In the app capabilities, ensure that remote notifications inside background modes is enabled.

Remote Notifications in Xcode

In the same tab, ensure that push notifications is enabled.

Push Notifications in Xcode

important

Silent push notifications will work even if users deny notification permissions in the app. The only way to disable them is to disable "Background App Refresh" in Settings or to turn on "Low Battery Mode".

Registering and receiving notifications

The following changes inside AppDelegate will register the SDK for push notifications and route HyperTrack notifications to the SDK.

Register for notifications

Inside didFinishLaunchingWithOptions, use the SDK method to register for notifications.

Swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
HyperTrack.registerForRemoteNotifications()
return true
}
Objective-C
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[HTSDK registerForRemoteNotifications];
return YES;
}

Register device token

Inside and didRegisterForRemoteNotificationsWithDeviceToken and didFailToRegisterForRemoteNotificationsWithError methods, add the relevant lines so that HyperTrack can register the device token.

Swift
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
HyperTrack.didRegisterForRemoteNotificationsWithDeviceToken(deviceToken)
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
HyperTrack.didFailToRegisterForRemoteNotificationsWithError(error)
}
Objective-C
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
[HTSDK didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
[HTSDK didFailToRegisterForRemoteNotificationsWithError:error];
}

Receive notifications

Inside the didReceiveRemoteNotification method, add the HyperTrack receiver. This method parses only the notifications sent from HyperTrack.

Swift
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
HyperTrack.didReceiveRemoteNotification(userInfo, fetchCompletionHandler: completionHandler)
}
Objective-C
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[HTSDK didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}

If you want to make sure to only pass HyperTrack notifications to the SDK, you can use the "hypertrack" key:

Swift
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
if userInfo["hypertrack"] != nil {
// This is HyperTrack's notification
HyperTrack.didReceiveRemoteNotification(userInfo, fetchCompletionHandler: completionHandler)
} else {
// Handle your server's notification here
}
}
Objective-C
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
if (userInfo[@"hypertrack"] != nil) {
// This is HyperTrack's notification
[HTSDK didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
} else {
// Handle your server's notification here
}
}

Start tracking

Now the app is ready to be tracked from the cloud. HyperTrack gives you powerful APIs to control device tracking from your backend.

To use the HyperTrack API, you will need the {AccountId} and {SecretKey} from the Setup page.

Track devices during work

Track devices when user is logged in to work, or during work hours by calling the Devices API.

To start, call the start API.

curl -X POST \
-u {AccountId}:{SecretKey} \
https://v3.api.hypertrack.com/devices/{device_id}/start

Get the tracking status of the device by calling GET /devices/{device_id} api.

curl \
-u {AccountId}:{SecretKey} \
https://v3.api.hypertrack.com/devices/{device_id}

To see the device on a map, open the returned embed_url in your browser (no login required, so you can add embed these views directly to you web app). The device will also show up in the device list in the HyperTrack dashboard.

To stop tracking, call the stop API.

curl -X POST \
-u {AccountId}:{SecretKey} \
https://v3.api.hypertrack.com/devices/{device_id}/stop

Track trips with ETA

If you want to track a device on its way to a destination, call the Trips API and add destination.

HyperTrack Trips API offers extra fields to get additional intelligence over the Devices API.

  • set destination to track route and ETA
  • set scheduled_at to track delays
  • share live tracking URL of the trip with customers
  • embed live tracking view of the trip in your ops dashboard
curl -u {AccountId}:{SecretKey} --location --request POST 'https://v3.api.hypertrack.com/trips/' \
--header 'Content-Type: application/json' \
--data-raw '{
"device_id": "{device_id}",
"destination": {
"geometry": {
"type": "Point",
"coordinates": [{longitude}, {latitude}]
}
}
}'

To get {longitude} and {latitude} of your destination, you can use for example Google Maps.

HyperTrack uses GeoJSON. Please make sure you follow the correct ordering of longitude and latitude.

The returned JSON includes the embed_url for your dashboard and share_url for your customers.

When you are done tracking this trip, call complete Trip API using the trip_id from the create trip call above.

curl -X POST \
-u {AccountId}:{SecretKey} \
https://v3.api.hypertrack.com/trips/{trip_id}/complete

After the trip is completed, use the Trips API to retrieve a full summary of the trip. The summary contains the polyline of the trip, distance, duration and markers of the trip.

curl -X POST \
-u {AccountId}:{SecretKey} \
https://v3.api.hypertrack.com/trips/{trip_id}

Track trips with geofences

If you want to track a device going to a list of places, call the Trips API and add geofences. This way you will get arrival, exit, time spent and route to geofences. Please checkout our docs for more details.

Dashboard

Once your app is running, go to the dashboard where you can see a list of all your devices and their live location with ongoing activity on the map.

Optional steps

Identify devices

All devices tracked on HyperTrack are uniquely identified using UUID. You can get this identifier programmatically in your app by calling deviceID after initialization. Another approach is to tag device with a name that will make it easy to distinguish them on HyperTrack Dashboard.

Swift
hyperTrack.setDeviceName("Device name")
Objective-C
hyperTrack.deviceName = @"Device name";

You can additionaly tag devices with custom metadata. Metadata should be representable in JSON.

Swift
if let metadata = HyperTrack.Metadata(rawValue: ["key": "value"]) {
hyperTrack.setDeviceMetadata(metadata)
} else {
// Metadata can't be represented in JSON
}
Objective-C
NSDictionary *dictionary = @{@"key": @"value"};
HTMetadata *metadata = [[HTMetadata alloc] initWithDictionary:dictionary];
if (metadata != nil) {
[self.hyperTrack setDeviceMetadata:metadata];
} else {
// Metadata can't be represented in JSON
}

Add a geotag

Use this optional method if you want to tag the tracked data with an event that happened in your app. E.g. user marking a task as done, user tapping a button to share location, user accepting an assigned job, device entering a geofence, etc.

The process is the same as for device metadata:

Swift
if let metadata = HyperTrack.Metadata(rawValue: ["status": "PICKING_UP"]) {
hyperTrack.addGeotag(metadata)
} else {
// Metadata can't be represented in JSON
}
Objective-C
NSDictionary *dictionary = @{@"status": @"PICKING_UP"};
HTMetadata *metadata = [[HTMetadata alloc] initWithDictionary:dictionary];
if (metadata != nil) {
[self.hyperTrack addGeotag:metadata];
} else {
// Metadata can't be represented in JSON
}

Frequently Asked Questions

Please review this guide to get answer to these questions:

Why doesn't my iOS app start tracking in the background state?

Make sure your app has location and motion permissions to start tracking.

Get permissions before your app is in the background state

For example, if your app does the following steps:

  • User logs in
  • Starts a shift
  • App calls your app backend which calls HyperTrack API to create a trip

then at this point the app can already be in the background because the user has already switched to other tasks. HyperTrack cannot start tracking on the device for the trip as permissions cannot be requested while the app is in the background state.

important

If location and motion permissions are not granted in the foreground prior to the app going to the background state, HyperTrack SDK cannot request them to start location tracking in your app in the background state.

That’s why it’s important to make sure that the app has all required permissions before the user can start a shift.

To make this happen, your app needs to use CLLocationManager and CMMotionActivityManager to request authorization.

Usually apps detect current permissions state first and, if they are not granted, show a special screen allowing the user to grant them right there, or if they were denied to switch them on in Settings.app.

This way, when the user starts a new shift, push notification would reach the app, the app would have all the necessary permissions to track.

Why did my iOS app stop tracking?

OS regularly drops application memory when it needs more memory for the app currently in foreground.

Test memory usage on device

Quickest way to check this is to run a game or open a Camera app and record a video. The OS sorts all apps frozen in the background by memory footprint and starts dropping memory one by one starting with the largest ones.

When iOS does this, it creates a file called "JetsamEvent", which you can see in Settings > Privacy > Analytics & Improvements > Analytics Data.

For more details see this Apple Developer Note.

note

This is typical iOS behavior and no app can assume that the OS will always keep memory for frozen apps.

iOS versions 13.2 and 13.2.1 had a bug which caused the scheduler to drop memory for the frozen apps more aggressively.

Also, your app should not rely on memory to be preserved and should store and then restore their state when the user launches the app.

Sometimes application bugs, like memory leaks, can cause the app to consume a lot of RAM, and become one of the first apps in the iOS list for the memory reclaim process.

Prevent excessive memory usage on iOS device

To prevent this, your app should be regularly profiled in Xcode using Instruments tool, configured with Leaks preset. It provides two views into the app memory called Allocations and Leaks.

Allocations allow you to see how much RAM your app is consuming and how this size grows and shrinks in response to user actions.

Leaks tool automatically reports when memory is created and would never be freed. HyperTrack SDK profiled in Instruments consumes under 1 MB of RAM and doesn't have memory leaks.

Good starting point can be Instruments documentation by Apple. iOS also sends low memory warnings to the app when it detects that it consumes too much.

It's also useful to checkout Apple guide on improving your app's performance.

If you’ll find any instances of HyperTrack SDK consuming too much RAM, or being a reason for abrupt terminations, please do not hesitate to contact us and provide crash reports or Instrument analysis files. Instruments sessions can be saved and shared.

Prepare your app to run in the background

iOS can also abruptly terminate the app upon entering the background. This can happen if, upon entering background, the app does not free resources and does not stop long running tasks, like timers and network requests.

The app without a reason to stay in the background has only a couple of seconds to stop all its activities and if it fails, the OS will force the termination.

The following guide by Apple provides all the steps the app needs to do upon entering background and can serve as a checklist.

Why does my application state disappear when my app is opened?

App state is not preserved in the background

App does not hold state when it goes to the background state. When the user returns to the app, it starts from scratch. iOS stops executing an app and freezes its memory when the user hides the app's screen.

The only exceptions are apps that actively do background work, such as:

  • downloading content
  • VoIP calls
  • active location tracking

There are three things that can happen to the app in a stopped and frozen state:

  • The app's memory is stored in RAM and when the user returns to the app execution continues where he left off
  • OS drops the app's memory, so when the user returns, the app needs to restore state manually
  • OS terminates app execution abruptly, crashing the app in the background
important

This is typical iOS behavior and no app can assume that the OS will always keep memory for frozen apps.

iOS versions 13.2 and 13.2.1 had a bug which caused the scheduler to drop memory for the frozen apps more aggressively.

Apps shouldn't rely on memory to be preserved and should store and then restore their state when the user launches the app.

A good starting point to learn how to manage restoring app state is newly updated UI state restoration guide.

What are the best practices for handling permissions on iOS?

In Human Interface Guidelines Apple recommends the following instructions below:

Request permissions only when they are needed in the flow of the app

If you app is centered around location tracking, then asking for permissions at the app launch can be understandable for users. On the other hand, if location tracking is just one of the features, then it makes sense to request them only when the feature is activated.

Provide short and specific purpose string

Purpose string should explain the value that location and motion tracking provides. Examples of motion tracking benefits: improves battery life by using algorithms based on motion tracking data, provides story-like details for historical tracking data, gives live feedback on current activity.

In addition a lot of great apps provide a special screen explaining the need for permissions before asking them. If permissions are denied you can guide the user to the specific page in the Settings.app to change permissions (see this guide for special deep-links for the Settings.app).

"Provisional Always" authorization state

On iOS 13 Apple introduced a new "Provisional Always" authorization state (see this StackOverflow answer for details).

In short:

  • there is no API to detect this state
  • during this state there are no location events in background
  • user sees his permissions as granted and sees "While Using" state in Settings.app
  • app sees permissions as granted with "Always" state.

HyperTrack is working on ways to detect this state and provide APIs that would enable app developers to display explanation screens that will guide the user back to Settings.app to switch permissions from "While Using" to "Always".

Why Access to Activity services has not been authorized?

You are running the Quickstart app on the iOS simulator, which currently does not support CoreMotion services.

important

You can test the app on real iOS devices only.