Version 0.6.0

  • Current version: 0.6.0
  • Previous versions
    • v0.5.3
    • v0.5.0
    • v0.4.0
    • v0.3.0
    • v0.2.0
    • v0.1.1
    • v0.1.0
  • Main features
    • Retrieves data in foreground and background,
    • Connects with the MOPRIM Cloud API to send data, retrieve user activities, correct activities.

Overview

The MOPRIM TMD SDK for iOS when installed and configured properly will gather data streams from the mobile device’s motion sensors to capture how its owner moves. Once the Moprim TMD service is started, the SDK automatically processes the data and synchronizes its results with our cloud. The final outputs of the TMD will be served through our cloud.

Pre-requisite: the MOPRIM TMD SDK requires that your project targets iOS 11 or later. The SDK has currently been tested on iOS 12, 13 and 14 devices.

Installing the MOPRIM TMD SDK

Before you start to use the MOPRIM TMD SDK in your iOS application, you will need to add the SDK as a dependency. The MOPRIM SDK also requires that you include some other dependencies into your project in order to function. If you do not have the MOPRIM TMD SDK, you can contact us to get a quote and developer API key.

Add the MOPRIM TMD SDK dependency

  • In your Xcode project, drag the MOPRIMTmdSdk.xcframework into your target’s Frameworks, Libraries and Embedded Content section, and make sure the Embed & Sign option is set.
  • The TMD SDK relies on AWS dependencies that you can download from this link.
  • From the downloaded frameworks, you will only need the following XCFrameworks:
- AWSAPIGateway.xcframework
- AWSAuthCore.xcframework
- AWSCognitoIdentityProvider.xcframework
- AWSCognitoIdentityProviderASF.xcframework
- AWSCore.xcframework
- AWSMobileClientXCF.xcframework
- AWSS3.xcframework
  • Drag them into your target’s Frameworks, Libraries and Embedded Content section, and make sure the Embed & Sign option is set.
  • From your project’s build settings tab, set Always Embed Swift Standard Libraries to Yes.

Configure your project

  • In your Info.plist, add the following keys in order to support location monitoring, motion monitoring, and background modes:
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>The apps monitors the user's mobility and requires the user's location to do so</string>

<key>NSLocationAlwaysUsageDescription</key>
<string>If the user is mobile, a significant location change will trigger the capture of the mobility data of the user</string>

<key>NSLocationUsageDescription</key>
<string>The apps monitors the user's mobility and requires the user's location to do so</string>

<key>NSLocationWhenInUseUsageDescription</key>
<string>The apps monitors the user's mobility and requires the user's location to do so</string>

<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
	<key>tmd.AccurateLocationPurpose</key>
	<string>The app monitors your mobility and requires your precise location to do so</string>
</dict>

<key>NSMotionUsageDescription</key>
<string>The app collects accelerometer data to evaluate the modes of transports used by the iOS device user</string>

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

The strings you associate with those keys will be presented to the user to ask for permission to access location and motion data. It is important that the user allows the app to access “Precise” location “Always and when in use” for the TMD to work correctly. It is also important that the user allows access to motion data.

Please note that starting from iOS 13, iOS first asks the user for “WhenInUse” authorization, and later, at a time that it deems appropriate, for “Always” authorization. The Transport Mode Detection will only work once the user allows the location to be accessed “always”.

Please note that starting from iOS 14, iOS allows the user to give access to only “Approximate” location. The Transport Mode Detection will only work once the user allows the “Precise” location to be accessed.

Using the MOPRIM TMD SDK

Initialization

The SDK requires an API key and an API enpoint configuration. It is recommended to configure the TMD in your AppDelegate’s didFinishLaunchingWithOptions method with a call to TMD#initWithKey:withEndpoint:withLaunchOptions:. If for some reason you cannot initialize the TMD from the AppDelegate, be sure to call TMD#initWithKey:withEndpoint:withLaunchOptions: before you do any other operation with the SDK.

import MOPRIMTmdSdk

(...)

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    // Configure the app to trigger Background Fetch events as regularly as possible.
    UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
    
    // Declare your API Key and Endpoint:
    let myKey = "MY_SDK_CONFIG_KEY"
    let myEndpoint = "MY_SDK_CONFIG_END_POINT"
            
    // Initialize the TMD:
    TMD.initWithKey(myKey, withEndpoint: myEndpoint, withLaunchOptions: launchOptions).continueWith { (task) -> Any? in
            if let error = task.error {
                NSLog("Error while initializing the TMD SDK: %@", error.localizedDescription)
            }
            else {
                // Get the app's installation id:
                NSLog("Successfully initialized the TMD with id %@", task.result ?? "<nil>")
            }
            return task;
    }
    return true
}
    

Additionally, the TMD SDK’s backgroundFetch method needs to be called from your AppDelegate’s performFetchWithCompletionHandler method in order to send the recorded data from the phone to MOPRIM’s API when the app is in the background:

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    // Run our background operations
    TMD.backgroundFetch().continueWith (block: { (task) -> Void in
        let tmdFetchResult:UIBackgroundFetchResult = UIBackgroundFetchResult(rawValue: (task.result!.uintValue))!
		// Call the completion handler with the UIBackgroundFetchResult returned by TMD.backgroundFetch(), or with your own background fetch result
        completionHandler(tmdFetchResult)
    })
}

To ensure that data is sent correctly to MOPRIM’s API, you should also call TMD#application:handleEventsForBackgroundURLSession:completionHandler: from your AppDelegate’s:

func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
    TMD.application(application, handleEventsForBackgroundURLSession: identifier, completionHandler: completionHandler)
}

It is also recommended that you make a call to the TMD’s applicationWillTerminate method from your AppDelegate’s applicationWillTerminate method in order to give a chance to the TMD to stop gracefully when the application is killed.

func applicationWillTerminate(_ application: UIApplication) {
    TMD.applicationWillTerminate()
}

Starting the TMD service

The TMD service can be started with:

TMD.start()

The TMD SDK will take care of asking user authorization to access location and motion data when it is first started. However, if you want to have control over when these permissions are asked to the user and what happens when they are denied, we recommend that you ask these permissions before starting the TMD for the first time.

Asking for permission to access location data

In your code, you can do that with an instance of CLLocationManager:

import CoreLocation

class ViewController: UIViewController, CLLocationManagerDelegate {
    let locationManager = CLLocationManager()
    
    func askLocationPermissions() {
        self.locationManager.delegate = self
        self.locationManager.requestAlwaysAuthorization()
    }
    
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        if #available(iOS 14.0, *) {
            let preciseLocationAuthorized = (manager.accuracyAuthorization == .fullAccuracy)
            if preciseLocationAuthorized == false {
                manager.requestTemporaryFullAccuracyAuthorization(withPurposeKey: "tmd.AccurateLocationPurpose")
                // Note that this will only ask for TEMPORARY precise location.
                // You should make sure to ask your user to keep the Precise Location turned on in the Settings.
            }
        } else {
            // No need to ask for precise location before iOS 14
        }
    }
}

Asking for permission to access motion data

In your code, using an instance of CMMotionActivityManager to start activity updates will prompt the user to allow motion access, like that:

import CoreMotion

let motionActivityManager = CMMotionActivityManager()
func askMotionPermissions() {
    if CMMotionActivityManager.isActivityAvailable() {
        self.motionActivityManager.startActivityUpdates(to: OperationQueue.main) { (motion) in
            print("received motion activity")
            self.motionActivityManager.stopActivityUpdates()
        }
    }
}

Stopping the TMD service

You can stop the TMD service with:

TMD.stop()

TMD lifecycle

Applications behave differently when they are in foreground and in background, and your app might get killed by the OS after being in the background for a while. If configured properly, your app can stay active in the background to some extent, and wake up to detect that the user started moving, and start the TMD.

In order to keep the TMD running in the background, the app must have access to location and motion data, and register to the “fetch” and “location” background modes, as mentionned in the Configure your project section.

Knowing if the TMD started properly

You can register a delegate of type TMDDelegate to be notified when the TMD starts and stops, or when it could not start or had to stop because of an error.

Fetching metadata about the TMD results on the Cloud

TMD metadata contains information about the information that our cloud knows about a user:

  • The first timestamp (in milliseconds) that we received data (i.e., start of the first recognized activity)
  • The last timestamp (in milliseconds) that we received data
  • The last timestamp of an uploaded TMD activity
  • The last timestamp of an uploaded location
  • The last timestamp where TMD was synced with the cloud
  • The last timestamp where location was synced with the cloud
  • The average daily co2 value for the community
  • The average daily distance value for the community
  • The average daily duration value for the community

More information about TMDCloudMetadata can be found in the appledoc.

In order to get the TMD Cloud metadata, please run the TMDCloudApi#fetchMetadata method.

TMDCloudApi.fetchMetadata().continueWith { (task) -> Any? in
	DispatchQueue.main.async {
		// Execute your UI related code on the main thread	
		if let error = task.error {
		    NSLog("fetchMetadata Error: %@", error.localizedDescription)
		}
		else if let metadata = task.result {
		    NSLog("fetchMetadata result: %@", metadata)
		}
		return nil;
	}
}

This method and its completion block execute in a background thread, and require the TMD to be initialized beforehand and an available internet connection.

The TMD SDK uses TMDTask to handle operations with the TMDCloudAPI. More information can be found in the appledoc

Fetching data from the MOPRIM Cloud

TMD Activities contains information about the recognized means of transport of the mobile phone carrier:

  • The unique id of the activity when stored in the local cache
  • The timestamp (in milliseconds) when the activity was last fetched from the MOPRIM Cloud
  • The timestamp (in milliseconds) when the activity started
  • The timestamp (in milliseconds) when the activity stopped
  • The label for the corrected activity if the activity was changed by the user manually
  • The label for the original TMD activity
  • A boolean indicating if the activity has been corrected by the user
  • The last update timestamp (in milliseconds) of the activity
  • The C02 value (in grams) for the activity
  • The distance (in meters) covered during the activity
  • The average speed (in km/h) during the activity
  • The encoded polyline of the locations crossed during the activity
  • The origin of the activity, i.e., label of the origin location or the location for stationary
  • The destination of the activity
  • A sync flag to check whether change made manually on the activity have been synced with the cloud

More information about TMDActivity can be found in the appledoc.

In order to get the list of TMD activities, please run the TMDCloudApi#fetchData:minutesOffset: method or alike.

TMDCloudApi.fetchData(date, minutesOffset: 0).continueWith { (task) -> Any? in
	DispatchQueue.main.async {
		// Execute your UI related code on the main thread	
		if let error = task.error {
		    NSLog("fetchData Error: %@", error.localizedDescription)
		}
		else if let data = task.result {
		    NSLog("fetchData result: %@", data)
		}
		return nil;
	}
}

This method and its completion block execute in a background thread, and require the TMD to be initialized beforehand and an available internet connection.

Manually correcting an activity label

In the event the TMD recognized a wrong activity or the discovered activity is not precise enough (e.g., motorized/road/car instead of motorized/road/car/electric for an electric car), it is possible to manually change the label of this activity by using the TMDCloudApi#correctActivity:withLabel: method.

TMDCloudApi.correctActivity(activity, withLabel: newLabel).continueOnSuccessWith { (task) -> Any? in
	DispatchQueue.main.async {
		// Execute your UI related code on the main thread	
		if let error = task.error {
		    NSLog("correctActivity Error: %@", error.localizedDescription)
		}
		else if let data = task.result {
		    NSLog("correctActivity result: %@", data)
		}
		return nil;
	}
}

This method and its completion block execute in a background thread, and require the TMD to be initialized beforehand.

The iOS SDK is configured via the MOPRIM Cloud. Therefore, the number of TMD activities that can be recognized (e.g., motorized/road/car, non-motorized/bicycle) can change everytime the TMD is reconfigured with the latest models. Please refer to the list of current activities.

Annotating an activity (i.e., add metadata)

The TMD SDK enables you to annotate an activity with more information than the one provided by the SDK natively. You can set the metadata for this activity by using the TMDCloudApi#annotateActivity method.

TMDCloudApi.annotateActivity(activity, withMetadata: metadata).continueOnSuccessWith { (task) -> Any? in
	DispatchQueue.main.async {
		// Execute your UI related code on the main thread	
		if let error = task.error {
		    NSLog("annotateActivity Error: %@", error.localizedDescription)
		}
		else if let data = task.result {
		    NSLog("annotateActivity result: %@", data)
		}
		return nil;
	}
}

This method and its completion block execute in a background thread, and require the TMD to be initialized beforehand.

It is possible to update the label and add metadata at the same time by using the TMDCloudApi#updateActivity:withLabel:withMetadata: method.

Fetching trips from the MOPRIM Cloud

TMD Trips are objects that group TMD Activities together as legs when the Moprim Cloud has determined that they are part of the same trip.

A TMDTrip contains the following:

  • The unique id of the trip when stored in the local cache
  • The timestamp (in milliseconds) when the trip was last fetched from the MOPRIM Cloud
  • The timestamp (in milliseconds) when the trip was last modified
  • The timestamp (in milliseconds) when the trip started
  • The timestamp (in milliseconds) when the trip stopped
  • The name of the main activity (mainMode) of the trip
  • The list of activities (legs) of the trip
  • A boolean indicating if the trip has been validated by the user
  • A boolean indicating if the trip is considered “completed” by the Moprim Cloud
  • The C02 value (in grams) for the trip
  • The distance (in meters) covered during the trip
  • The origin of the trip, i.e., label of the origin location or the location for stationary
  • The destination of the trip
  • A sync flag to check whether change made manually on the trip have been synced with the cloud

More information about TMDTrip can be found in the appledoc.

Similarly to TMD Activities, it is possible to fetch TMD Trip data from the cloud or from the cache, with the following methods:

Fetching TMD Trips will also fetch their TMD Activities.

Here is an example of fetching trips for a date:

TMDCloudApi.fetchTrips(for: date, minutesOffset:0).continueWith { (task) -> Any? in
	DispatchQueue.main.async {
		// Execute your UI related code on the main thread	
		if let error = task.error {
		    NSLog("fetchTrips Error: %@", error.localizedDescription)
		}
		else if let data = task.result {
		    NSLog("fetchTrips result: %@", data)
		}
		return nil;
	}
}

This method and its completion block execute in a background thread, and require the TMD to be initialized beforehand and an available internet connection.

Validating a trip

Validating a trip will mark it and its activities (or legs) as validated. This can be done by calling the following method: TMDCloudApi#validateTrip:

TMDCloudApi.validateTrip(trip).continueOnSuccessWith { (task) -> Any? in
	DispatchQueue.main.async {
		// Execute your UI related code on the main thread	
		if let error = task.error {
		    NSLog("validateTrip Error: %@", error.localizedDescription)
		}
		else if let data = task.result {
		    NSLog("validateTrip result: %@", data)
		}
		return nil;
	}
}

This method and its completion block execute in a background thread, and require the TMD to be initialized beforehand.

Annotating a trip (i.e., add metadata)

A trip can be updated with some metadata, or a reason for the trip. This can be done by calling the following method: TMDCloudApi#updateTrip:withReason:withMetadata:

TMDCloudApi.updateTrip(trip, withReason: "errands", withMetadata: metadata).continueOnSuccessWith { (task) -> Any? in
	DispatchQueue.main.async {
		// Execute your UI related code on the main thread	
		if let error = task.error {
		    NSLog("updateTrip Error: %@", error.localizedDescription)
		}
		else if let data = task.result {
		    NSLog("updateTrip result: %@", data)
		}
		return nil;
	}
}

This method and its completion block execute in a background thread, and require the TMD to be initialized beforehand.

Fetching statistics about the user mobility

TMD statistics contain information about a list of statistics for each type of activity and for a specific time period (weekend vs. weekdays and last X days vs. overall. For each value we provide the combined sum of CO2 emissions, the distance and duration of these activities for the user as well as for the community (daily average per user).

Check the appledoc for more information.

TMDCloudApi.fetchStatsForLast(nbOfDays).continueOnSuccessWith { (task) -> Any? in
	DispatchQueue.main.async {
		// Execute your UI related code on the main thread	
		if let error = task.error {
		    NSLog("fetchStats Error: %@", error.localizedDescription)
		}
		else if let data = task.result {
		    NSLog("fetchStats result: %@", data)
		}
		return nil;
    }
}

Check out the appledoc appledoc for more information about TMDStats and TMDStatsValue.

This method and its completion block execute in a background thread, and require the TMD to be initialized beforehand and an available internet connection.

Allowing the SDK to perform uploads using Mobile Data

The TMD SDK has an option to enable or disable the upload of mobility data to our cloud using Mobile Data. When this option is disabled, uploads will only happen over Wifi.

This option is disabled by default. You should therefore call the following method (after initializing the TMD for example), if you want uploads to be done using Mobile Data as well:

TMD.setAllowUploadOnCellularNetwork(true)

Forcing the upload of TMD data to our cloud

Our system usually uploads the data automatically to our cloud periodically (when the OS triggers a background fetch, or when the service is started), but it is possible for the application developer to force the upload of data to our cloud.

The current modality is never synchronized with the cloud as it is not considered as completed.

To force the upload of data to our cloud, use the following method:

TMDCloudApi.uploadData().continueWith { (task) -> Any? in
	DispatchQueue.main.async {
		// Execute your UI related code on the main thread	
		if let error = task.error {
		    NSLog("uploadData Error: %@", error.localizedDescription)
		}
		else if let metadata = task.result {
		    NSLog("uploadData success: %@", metadata)
		}
		return nil;
    }
}

TMDUploadMetadata contains information about timestamp and number of locations uploaded to the Cloud with the call as well as the information for TMD activities.

For more information about TMDUploadMetadata, check out the appledoc.

This method and its completion block execute in a background thread, and require the TMD to be initialized beforehand and an available internet connection.

You can also register a delegate of type TMDUploadDelegate to be notified when an upload starts or ends with the method:

TMD.setUploadDelegate(delegate)

Or at upload time with:

TMDCloudApi.uploadDataWithDelegate(delegate).continueWith { (task) -> Any? in
	DispatchQueue.main.async {
		// Execute your UI related code on the main thread	
		if let error = task.error {
		    NSLog("uploadData Error: %@", error.localizedDescription)
		}
		else if let metadata = task.result {
		    NSLog("uploadData success: %@", metadata)
		}
		return nil;
    }
}

For more information about TMDUploadDelegate , check out the appledoc.

Allowing automatic download of new data

It is possible to allow the TMD SDK to automatically download new data after the Moprim Cloud has received and analyzed newly uploaded data. To do so, call this method before starting the TMD:

TMD.setAllowAutoFetch(true)

The auto-fetch is disabled by default. You can use TMD#isAutoFetchAllowed() to check its value.

Registering for notifications on updated data

It is possible to observe NSNotifications that will happen whenever TMDTrip or TMDActivity objects have been changed in the cache. The names of the NSNotifications and the keys that can be used with their corresponding userInfo dictionary can be accessed from TMDNotifications. For more information about TMDNotifications, check out the appledoc. Here is an example of registering to notifications to print in the console what has been changed in the cache:

func registerToNotifications(){
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(self.didUpdateActivities(notification:)),
                                           name: NSNotification.Name(rawValue: TMDNotifications.didUpdateActivitiesNotificationName()),
                                           object: nil)
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(self.didUpdateTrips(notification:)),
                                           name: NSNotification.Name(rawValue: TMDNotifications.didUpdateTripsNotificationName()),
                                           object: nil)
}

@objc func didUpdateActivities(notification: Notification){
    if let dict = notification.userInfo {
        if let inserted = dict[TMDNotifications.insertedActivitiesKey()] as? [TMDActivity] {
            NSLog("didUpdateActivities.inserted \(inserted.count) activities")
            for activity in inserted {
                NSLog("didUpdateActivities.inserted:\(activity.activityId) - \(activity.activity())")
            }
        }
        if let updated = dict[TMDNotifications.updatedActivitiesKey()] as? [TMDActivity] {
            NSLog("didUpdateActivities.updated \(updated.count) activities")
            for activity in updated {
                NSLog("didUpdateActivities.updated:\(activity.activityId) - \(activity.activity())")
            }
        }
        if let deleted = dict[TMDNotifications.deletedActivitiesKey()] as? [TMDActivity] {
            NSLog("didUpdateActivities.deleted \(deleted.count) activities")
            for activity in deleted {
                NSLog("didUpdateActivities.deleted:\(activity.activityId) - \(activity.activity())")
            }
        }
    }
}

@objc func didUpdateTrips(notification: Notification){
    if let dict = notification.userInfo {
        if let inserted = dict[TMDNotifications.insertedTripsKey()] as? [TMDTripNotification] {
            NSLog("didUpdateTrips.inserted \(inserted.count) trips")
            for trip in inserted {
                NSLog("didUpdateTrips.inserted:\(trip.tripId) - \(trip.timestampStart) - \(trip.timestampEnd)")
            }
        }
        if let updated = dict[TMDNotifications.updatedTripsKey()] as? [TMDTripNotification] {
            NSLog("didUpdateTrips.updated \(updated.count) trips")
            for trip in updated {
                NSLog("didUpdateTrips.updated:\(trip.tripId) - \(trip.timestampStart) - \(trip.timestampEnd)")
            }
        }
        if let deleted = dict[TMDNotifications.deletedTripsKey()] as? [TMDTripNotification] {
            NSLog("didUpdateTrips.deleted \(deleted.count) trips")
            for trip in deleted {
                NSLog("didUpdateTrips.deleted:\(trip.tripId) - \(trip.timestampStart) - \(trip.timestampEnd)")
            }
        }
    }
}

Possible errors returned by the MOPRIM TMD SDK methods

The list of possible errors returned by any TMD method is available in the appledoc

Using the TMD SDK in a React Native project

We currently don’t provide a React Native module to use the TMD SDK from a React Native project. However, it is possible to make one by following the recommendations from the React Native documentation on how to make an iOS Native Module.

As a starting point, here is how you would start and stop the TMD from React Native:

  • First, in your iOS project (.xcworkspace file), add a new Objective-C class that looks like the following:
//  RCTTmdModule.h

#import <React/RCTBridgeModule.h>
@interface RCTTmdModule : NSObject <RCTBridgeModule>
@end
//  RCTTmdModule.m

#import "RCTTmdModule.h"
#import <MOPRIMTmdSdk/MOPRIMTmdSdk.h>

@implementation RCTTmdModule

// Export a module named RCTTmdModule:
RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(start)
{
  [TMD start];
}

RCT_EXPORT_METHOD(stop)
{
  [TMD stop];
}

@end
  • Then, in your React Native project, add the TMD module:
//  NativeTmdModule.js

import { NativeModules } from 'react-native';
const { TmdModule } = NativeModules;

interface TmdInterface {
  start(): void;
  stop(): void;
}

export default TmdModule;
  • You can now use your TMD module from your React Native app:
//  App.js

import TmdModule from './NativeTmdModule';

const onPressTmdStart = () => {
  TmdModule.start();
};
const onPressTmdStop = () => {
  TmdModule.stop();
};

Changelog

  • Version 0.6.0

    • Added support for TMDTrip.
    • Added the possibility to be notified when TMDTrip or TMDActivity objects are updated in the cache.
    • Added the possibility to automatically fetch new data after an upload.
    • TMDCloudMetadata timestamps are now all in milliseconds.
    • TMDUploadMetadata timestamps are now all in milliseconds.
  • Version 0.5.3

    • Distribution as an XCFramework.
    • Added the TMDFitnessTrackingNotDetermined error type.
    • Bug fixes and improvements.
  • Version 0.5.2

    • Bug fixes and improvements.
  • Version 0.5.1

    • Bug fixes and improvements.
  • Version 0.5.0

    • iOS 14 compatibility.
    • Added a delegate to be notified of upload events.
    • The TMD SDK now lets the app run in the background without interruption.
    • Bug fixes and improvements.
  • Version 0.4.0

    • Bug fixes and improvements.
  • Version 0.3.0

    • Community values.
    • Bug fixes and improvements.
  • Version 0.2.0

    • Uploads to the cloud have been improved.
    • TMDTasks are executed in a background thread by default.
    • Support for the way location authorization is handled in iOS 13.
  • Version 0.1.1

    • Errors are better handled.
    • Bug fixes.
  • Version 0.1.0

    • Initial release of the iOS SDK

Contact

Contact us for getting a quote for the SDK.