Favendo GmbH

Note
This document is valid for Backspin iOS SDK 1.2.0-beta.1 Build 92 This is a preliminary Beta release. The documentation might not be complete, and the version is not tagged as stable, yet.

1. List of Functions

1.1. Provided by the iOS SDK

1.1.1. Positioning

The SDK offers beacon based indoor-positioning including bluloc-exclusive crypto-beacon (rolling-id mode) support. Since iOS SDK 1.1.x the positioning includes beacon-region monitoring to automatically start positioning and beacon-proximity ranging in the background once the user enters a beacon-region. The app is not required to be active or suspended in background.

The positioning can be configured with a number of “accuracy”-settings for their respective use cases (e.g. a power-saving-mode if positioning is in background for location-based-notifications only).

Note
Since the monitoring is done by iOS, it is impossible to precisely predict when the app will be woken up and receive a region entered event. A number of factors affect the delay between the enter event and its notification: Battery state (or the in iOS 9 introduced Low Power Mode), the number of apps on the device monitoring beacon-regions, the overall memory-consumption, and how recently the device booted. Technically, the delay can take up to 15 mintues. However, empirically the delay usually takes 10-30 seconds at the maximum.

Some iOS restrictions apply:

  • no more than 20 regions PER APP are allowed, including custom created geo-regions, e.g. CLCircularRegions . Each unique beacon-UUID leads to 1 region being monitored.

    • See Apple Documentation for Region Monitoring:

    • Be judicious when specifying the set of regions to monitor. Regions are a shared system resource, and the total number of regions available system wide is limited. For this reason, Core Location limits to 20 the number of regions that may be simultaneously monitored by a single app. To work around this limit, consider registering only those regions in the user’s immediate vicinity.

  • As mentioned in the section Custom Region Monitoring, you need to add all custom UUIDs that are monitored with an own CLLocationManager to the whitelist of BackspinSDKController: whiteListBeaconUuidsForBeaconMonitoring.

  • Background-activity in iOS (8 & 9) stops after 3 minutes, if the user does not open the app. Therefore, no beacon-proximity-notifications, location-based-messages, or location-updates will be delivered past 3 minutes after the beacon-region was entered. These 3 minutes are merely an empirically observed value, there is no officially documented time-span from Apple. You can evaluate this yourself by observing the backgroundTimeRemaining property in UIApplication while running a background task.

1.1.2. Navigation

Turn By Turn in Whitelabel

The SDK provides a navigation-path between two given coordinates. Initially, both coordinates are mapped onto the navigation path, the actual route is subsequently calculated between these two intersection points. A set of target coordinates can also be defined, from which the nearest one will be targeted - e.g. to navigate to a shop that has multiple entrances. In case a suitable KML is provided, the route can be requested with additional qualifications such as barrier-freedom.

Likewise, additional turn-by-turn-directions to aid the visual output can be requested for any given route, as in the example screenshot of the Backspin Whitelabel App.

1.1.3. Image Downloader & Level/Floor-Plans

The SDK provides methods to download and cache images with custom resolutions as well as level-plans with their respective images.

If a language is specified via the currentLanguageDesignator delegate method, only images with the corresponding language identifier will be downloaded provided that they have been previously uploaded through the Backspin Dashboard. Images for each language and resolution are cached separately via NSURLCache.

1.1.4. Location Based Notifications

There are two different types of notifications supported by the SDK:

  • Beacon Proximity Range

    • In this case, beacons trigger notifications if they are approached. The required proximity can be qualified as IMMEDIATE, NEAR, or FAR. These values are approximations for distances around 0.5 meter, 3-5 meters, and 5+ meters. The exact distances at which notifications are fired might minimally vary with each setup and beacon.

Note
An enter event for a notification defined as FAR is also triggered if one is in the NEAR or IMMEDIATE proximity range.
  • Zone/Location-Based

    • Zone and location-based trigger types come in three flavors: Enter, Exit, and Dwelltime. The latter will trigger if the user has spent a previously specified amount of minutes (max. 120) within the given zone or at the given location.

    • While offer- and venue-triggered notifications are separated in the dashboard, they are internally treated identically, since all notifications refer to their respective venue location. Therefore, if an offer does not have any linked venue, it cannot be processed.

Note
Since iOS stops any background-task after three minutes, any notifications requiring more than 2 minutes will not fire if the app is in background during that time.

1.1.5. Apple Push Notifications / App Account

Backspin sends push notifications via the Apple Push Notification Services (APNS). As explained in the Apple Documentation, you need to enable the service in the Apple Member Center, upload the certificate to the Backspin server and implement the UIAppDelegate callback:

- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;

The deviceToken can be used to call the SDK setup method to link it with its implicitly created user-account. Alternatively, it can be manually linked with registerDeviceWithApnsTokenAtServer:

Important
While in Backspin iOS SDK v1.1.x the account registration was done separately and optionally, it is now mandatory during the setup. Also, the app-account-id is not returned in a completion-block at registerDeviceWithApnsTokenAtServer: anymore, but rather available directly once the setup-completion-block has been called.
Note
Since REST v2 / Backspin iOS SDK v1.2.x there is an additional distinction between user-account and app-account. The app-account is set up on startup and linked with the APNS-token to send pushes and save the current language. The user-account can be used to handle user-specific data like the Backspin user profile, likeables, or leaflet. As of now these two accounts have the same ID. See User Accounts

1.1.6. Analytics

For several events in the SDK, analytics can be generated and transmitted to the server. Analytics can be configured for each event type independently. The following types are supported:

  • Indoor Location Updates

    • location_updated

  • Navigation Start

    • event has to be forwarded by SDK-user

    • key “navigation_started

  • Navigation Cancelled

    • event has to be forwarded by SDK-user

    • key “navigation_cancelled

  • Navigation target reached

    • is created automatically if a navigation is started in proximity of the target

    • key “navigation_targetreached

  • Notification created

    • i.e. the notification-object is handed out to the SDK-user

    • key “notification_created

  • Notification “presented”

    • event has to be forwarded by SDK-user

    • key “notification_confirmed

By implementing the BSSDKAnalyticsTypeHandler protocol, custom-defined analytics can be transmitted to the server through SDK by calling queueAnalyticsEventWithType:andAnalyticsData: The events will be queued and persisted until they are successfully uploaded. Note that they might not be accessible via dashboard by default.

1.1.7. Model-Store & Server-API

The Backspin iOS SDK contains a rudimentary API for the local model-store which allows downloads of either generic models or plain JSONs. There is an additional API for further REST calls, e.g. for User Accounts.

1.2. Additionally provided by Backspin server

The REST API offers some extended functionality that is currently not covered by the v1.2.0 Beta iOS SDK (Mid June 2016 A.D.). In the following examples, the placeholders {appid}, {appaccountid}, {useraccountid}, {rootscopeid}, and {language} can be used in server-requests directly (if using the SDK-methods) and will be replaced with the currently set values.

1.2.1. User Accounts

Not to be mixed up with the app-account described in Apple Push Notifications / App Account.

For now, the user-account is identical to the app-account. It is a placeholder for an upcoming feature which will allow sharing of an account across multiple devices.

For each app a profile-config needs to be set up in the dashboard. The (filled out) profile can be downloaded and managed via

  • /rest/v2/{appid}/{appaccountid}/ios/accounts/{useraccountid}/profile/{language}

For each account, you can add venues or offers on a likeables list (will be added as SDK-function in the final release of Backspin iOS SDK v1.2):

  • /rest/v2/{appid}/{appaccountid}/ios/accounts/{useraccountid}/likeables/{rootscopeid}/{language}

For each account, you can add offers on a leaflet:

  • /rest/v2/{appid}/{appaccountid}/ios/accounts/{useraccountid}/offerleaflet/{rootscopeid}/{language}

2. Prepare Your Project

From version 1.1 on, the SDK is a dynamic framework (this is due to its swifty nature). You therefore need to enable the “Embedded Content Contains Swift Code” setting. For more detail, see the Manual Installation section below.

If using the source/dependency/library manager Cocoa Pods, the installation is straightforward. Paste the following line inside your podfile:

pod 'Backspin-iOS-SDK', :podspec => "http://sdk.favendo.de/Backspin-iOS-SDK/Backspin-iOS-SDK.podspec.json"

You might need to replace the linked .podspec.json file with Backspin-iOS-SDK_1.2.0-beta.1.92.podspec.json

Since the SDK contains Swift code, you need to configure CocoaPods to use frameworks and work with iOS 8.0 upward. Insert the following at the top of your podfile:

platform :ios, "8.0"
use_frameworks!
Note
This setting might affect other pods.

2.2. Manual Installation

The frameworks can also be installed manually.

Extract the ZIP and add all frameworks from the “/Framework” folder to your framework as embedded: Select your target(s), go to “General” and drag the following frameworks from the folder above into the “Embedded Binaries” section:

  • BackspinSDK.framework

  • FavendoCommon.framework

  • FavendoBackspinCore.framework

  • FavendoPositioning.framework

  • FavendoNavigation.framework

2.2.1. External Dependencies

The Backspin iOS SDK has two external framework dependencies that need to be added to your project manually:

The SDK is linked with the following versions of these frameworks:

  • AFNetworking: 3.1.0

  • CocoaLumberjack: 2.3.0

The SDK has been tested to work with AFNetworking 2.4+, in particular 2.4.1, 2.5.4, and 2.6.3, though there is no guarantee that every feature works as reliable as with 3.1.

2.2.2. Adjust Project Settings

  1. In Xcode, select your project and target.

  2. Select the Build Settings tab.

  3. Search for “Other Linker Flags” in the provided search bar.

    • Add -ObjC to the “Other Linker Flags” setting.

    • Add -l “sqlite3” to the “Other Linker Flags” setting

  4. Search for “Build Options”

  5. Set “Embedded Content Contains Swift Code” to “Yes

  6. Set “Enable Bitcode” to “No

  7. Set “Enable Modules (C and Objective-C)” to “Yes

  8. Set “Link frameworks automatically” to “Yes” or follow the next section

2.2.3. Further Requirements

If “Link frameworks automatically” is set to “No”, these Apple frameworks need to be added to the project manually:

  • Accelerate.framework

  • AudioToolbox.framework

  • CoreBluetooth.framework

  • CoreLocation.framework

  • CoreMotion.framework

  • Foundation.framework

  • ImageIO.framework

  • MapKit.framework

  • MobileCoreServices.framework

  • Security.framework

  • SystemConfiguration.framework

  • UIKit.framework

2.3. Enable Location Services

To be able to access the current location of the user, you have to ask about permission to do so.

For different platforms there are different configurations needed:

iOS 8: Add the NSLocationWhenInUseUsageDescription key to the Information Property List. Add a custom string value describing why you have to access the user’s current location.

Note
For beacon-region-monitoring the “NSLocationAlwaysUsageDescription” key needs to be added as well. For background updates, you should also add “Continued use of GPS running in the background can dramatically decrease battery life” to the App-Store description. While you can translate it to each supported language, yet it is highly recommended to keep the original in English as well!

iOS 9: In order to work with indoor-positioning/location-based-content/beacon-proximity in the background, you need to enable the “Location Updates” Background-Mode. If you do not use any of these features in background mode, you should restrain from setting this flag. Otherwise it is likely for Apple to question its use during the review process. See more detail in the Troubleshooting / FAQ section and the official documentation Apple Documentation for CLLocationManager.

Background Modes

2.4. Logging with CocoaLumberjack

This will only concern you if you are using CocoaLumberjack as a logging framework.

If so, the unique Backspin iOS SDK logging-flags will not interfere with the default ones. However, in order to manage logging-levels, all current DDTTYLoggers will be removed on calling setupBackspinWithApiKey:. A default logger is added again via [DDLog addLogger:[DDTTYLogger sharedInstance]]. If this doesn’t suit your needs, you can change it in the success or error-block of the setup-method. All other Cocoalumberjack loggers will not be affected.

2.5. Custom Region Monitoring

If you are do custom Beacon Region Monitoring with the CLLocationManager, you should notify the BackspinSDK about the regions that are monitored. When starting monitoring/positioning, all current regions are removed from monitoring and the current positioning-regions are added again. This is done to ensure there are no dangling/unused regions if the positioning-UUID is changed.

Obviously this leads to the removal of any custom created beacon-regions that are monitored somewhere else in the app. You need to whitelist these regions for the SDK to keep them. Before calling setupBackspinWithApiKey, you need to add your UUIDs to this set:

[[BackspinSDKController sharedInstance].whiteListBeaconUuidsForBeaconMonitoring addObject:@"CUSTOM_UUID_THAT_IS_MONITORED"];

Geo-Regions (CLCircularRegion) are NOT affected by the cleanup.

3. Start Using the Backspin iOS SDK

3.1. Import the SDK Header

In order to use any functionality of the Backspin iOS SDK, make sure you import the header file.

#import <BackspinSDK/BackspinSDK.h>

3.2. Setup the Controller

The BackspinSDKController is the main interface for communicating with Backspin.

  • setup the environment

  • download Backspin models

  • download level-plans and images from Backspin

  • start indoor location updates

  • calculating navigation paths

  • handle notifications

  • etc.

for an extensive list of functions, see the sections List of Functions

In the following section you’ll see how to set up the controller and implement a delegate method to receive location updates. If you are using crypto-beacons with rolling-ids, make sure you configure the start-region-monitoring or beacon-ranging methods accordingly (see code-example below).

Warning
Remember to call setupBackspinWithApiKey: before any other call, the SDK will throw exceptions if unauthenticated.

Two properties should be set before calling setupBackspinWithApiKey:. 1.) The root-scope-id, otherwise a random scope-id will be used for the initial model-download (this is relevant only if you have more than one root-scope-id, e.g. multiple root-venues). 2.) As mentioned in Custom Region Monitoring, add custom monitored beacon UUIDs to whiteListBeaconUuidsForBeaconMonitoring.

Important
Since version 1.2 all data is to be requested manually, nothing will be downloaded automatically. While the setup method triggers the download of root-venues and creation of an app-account, it is required to call triggerContentUpdateFromServer or requestModelDownloadOrUpdateForRestEndpoint: before using positioning, navigation, or notifications. See also Model-Store & Server API for the API to request models from the server.
// ...

@interface YOUR_CLASS <BackspinSDKControllerDelegate, BackspinSDKControllerDatasource>

// ...

	[BackspinSDKController sharedInstance].delegate = self;
	[BackspinSDKController sharedInstance].datasource = self;

	// set the root-scope-id, otherwise a random one will be selected
	// only relevant if there are multiple root-scopes
	[[BackspinSDKController sharedInstance] setRootScopeId:23];

	// add this UUID to the whitelist, so it will be kept during monitoring-region-cleanup
	[[BackspinSDKController sharedInstance].whiteListBeaconUuidsForBeaconMonitoring addObject:@"CUSTOM_UUID"];

	[[BackspinSDKController sharedInstance] setupBackspinWithApiKey:@"YOUR_API_KEY" success:^{

		// download data now (required for positioning to start!)
		[[BackspinSDKController sharedInstance] triggerContentUpdateFromServer];

		// Set some properties to fit your needs
		[BackspinSDKController sharedInstance].positioningUseNavigationPathSnapping = YES;

		// Now start region monitoring
		// the beacon-ranging and indoor-positioning will start, if an iBeacon with the given UUID is monitored
		[[BackspinSDKController sharedInstance] startMonitoringPositioningBeaconRegionWithUUID:@"YOUR_UUID" cryptoBeacons:YES];

	} errorHandler:^(NSError *connectionError) {
		// Handle error
	}];

// ...

#pragma mark BackspinSDKControllerDelegate

- (void)calculatedNewIndoorLocation:(CLLocation *)indoorLocation {

    // Do something with the location object

    ...

    // use best accuracy setting if the position is on the same level that is currently displayed
    if(indoorLocation.altitude == self.displayedLevelnumber) {
        // best (practice) accuracy preset with all tweaks
        [[BackspinSDKController sharedInstance] enablePositioningAccuracySetting:BSSDKPositioningAccuracySettingBestAccuracy];
    }
    // otherwise the user does not see the position, so it can be “less accurate”
    else {
        // otherwise set power-saving preset
        [[BackspinSDKController sharedInstance] enablePositioningAccuracySetting:BSSDKPositioningAccuracySettingPowerSavingMode];
    }

}

For further implementation details look at the public header files.

4. Positioning

4.1. General

The following methods are required even if your App is confined to beacon-proximity-notifications (without indoor-positioning). In the end all methods will trigger/handle beacon-ranging.

If no positioning-beacons are available in the backend but notifications are linked to proximity beacons, monitoring will be started automatically.

Important
Beacon-monitoring does require enabling Enable Location Services and setting the AlwaysUsage key! WhenInUse will require the SDK user to trigger beacon ranging manually via the startIndoorPositioningWithBeaconUUID: method.
Important
If no positioning is needed but beacon-proximity-notifications is used with crypto-beacons, you need to start monitoring or positioning explicitly in order to activate the crypto-beacon setting: e.g. startMonitoringPositioningBeaconRegionWithUUID:cryptoBeacons (see next section)
Warning
There should never be more than one "crypto-beacon" settings for the same UUID. It should preferably be also avoided to use different crypto-beacon settings for different UUIDs. Currently, proximity-beacons can only be recognized as crypto-beacons if positioning/monitoring was started with the cryptoBeacons flag as well.

4.2. Monitoring

Since iOS 7.1, beacon region monitoring is reliable and responsive enough to be preferable to manual positioning ranging.

If the positioning is only used to display the position on the map and will be stopped if the map is invisible, monitoring might not be needed. In scenarios that require beacon responsiveness in the background, e.g. for proximity-notifications (see Location Based Notifications) which fire in the background upon entering a venue, monitoring makes more sense.

Monitoring in iOS means, the app can be woken up by iOS and to start ranging/positioning and also deliver Location Based Notifications in the background. The app is active for up to 10 seconds (if background mode is disabled) or 3 minutes (if enabled). After that time, the App will be shut down again. Keep in mind the considerations mentioned at Notes about iOS Background Modes.

Two different methods for positioning can be used:

// monitoring, trigger ranging-start once the user enters the region
// requires the "AlwaysUsage" permission to work, even in foreground
[[BackspinSDKController sharedInstance] startMonitoringPositioningBeaconRegionWithUUID:"UUID" cryptoBeacons:false];

// start positioning/ranging NOW, regardless if beacons are around or not
// only requires the "WhenInUse" permission
[[BackspinSDKController sharedInstance] startIndoorPositioningWithBeaconUUID:"UUID" cryptoBeacons:false];

There are three different methods to stop beacon-ranging / monitoring:

// this will stop ranging for the given UUID.
// if monitoring has been enabled for this UUID, it might be possible that positioning starts again, if device is still inside the beacon-region
[[BackspinSDKController sharedInstance] stopIndoorPositioningWithBeaconUUID:"UUID"];

// this will stop monitoring of all regions with this UUID
// if currently inside this beacon-region and ranging is going on, this will be stopped immediately
[[BackspinSDKController sharedInstance] stopMonitoringPositioningBeaconRegionWithUUID:"UUID"];

// if andStopBeaconMonitoring is set to true, this is essentially a combination of the two methods above.
// Will stop beacon-ranging immediately and also remove this beacon-region from being monitored.
[[BackspinSDKController sharedInstance] stopIndoorPositioningWithBeaconUUID:"UUID" andStopBeaconMonitoring:true];

4.3. Accuracy Settings

There are two settings presets which bundle a number of specific configurations to optimize the SDK for certain scenarios.

  • No Preset This is the default "preset", all positioning-configuration settings are set to their default values. Their appropriateness depends on the use case. If the user-position is not simply used for location-based-content notifications but, for instance, directly displayed on a map, the default settings are not ideal. While the API allows to override this preset, it is not recommended. (It can be thought of as an internal settings preset.)

  • Power Saving mode This preset is recommended if position accuracy is not of primary concern. It disables various filters to save computing power. A possible use case contains location-based-content notifications to fire in the background. The effect of less accurate position updates on the outcome is negligible here, making it advisable to save battery life. Similarly, if the map displays another level than the current position, accuracy only to the extend of the current level/region is required. The settings in this preset are exhaustive, there is no need to change them manually.

  • Best Accuracy Ideal if the app-user is directly looking at her position. While the default values represent a general "best practice", their ideality depend on the beacon-installation at sight. Trying out a variety of settings is recommended. You can also use setPositioningConfiguration:forSetting:forAccuracySetting:BSSDKPositioningAccuracySettingBestAccuracy to change presets for this setting.

Tip
Even if confined to a specific installation, there is not necessarily a perfect settings preset, as some areas/levels might work best with different settings. In a large space for instance, Navigation Path Snapping can lead to less accurate results than in an area with narrow paths. (An additionally factor is the provided KML.)

While the use of these presets are not required, they are recommended in terms of user experience.

// inside mapview
- (void) calculatedNewIndoorPosition:(CLLocation *)newCalculatedPosition {

	if((NSInteger)newCalculatedPosition.altitude == self.mapView.displayedLevelNumber) {
		[[BackspinSDKController sharedInstance] enablePositioningAccuracySetting:BSSDKPositioningAccuracySettingBestAccuracy];
	}
	else {
		[[BackspinSDKController sharedInstance] enablePositioningAccuracySetting:BSSDKPositioningAccuracySettingPowerSavingMode];
	}

	...
}

4.4. Positioning Configurations

These settings are explained in the BSSDKPositioningConfiguration enum which can be found in the header-file <BackspinSDK/BackspinSDKControllerPositioningConfiguration.h>. The default setting for each configuration per preset is listed there.

Each of these settings can be set once the success block of the setup method has been called. At this point, the accuracy-presets can be activated as well.

  • ActivateNavigationPathSnapping

    • Snaps the indoor-location to the nearest navigation-path to make the position marker more stable. This is features has been shown to be generally helpful. In certain unfavorable conditions however, for instance if the real position is right between two paths, the calculated position can jump back and forth between the two snapped positions. Since they are artificially snapped, the distance of those jumps can be larger than without this features. Also, keep in mind that path snapping only works if a KML has been provided. Enabling this configuration without a KML will render effectless.

  • NavigationPathSnappingDistance

    • Sets the threshold for path snapping. If the nearest navigation-path is beyond it, the path-snapping is ignored for this position update. Has been shown to be useful for large spaces with few paths which are fairly separated.

  • ActivateMagneticPathSnapping

    • Keeps the position snapped to the path, considering prior updates. If there are two parallel paths near each other, this can avoid arbitrary jumps between them. Has been proven useful in installations that contain long corridors or supermarket isles. However, if the initial path which will be snapped to magnetically is false, the result is worse. The calculated position can also stick to corners/intersections at sharp turns if performed quickly. Works only if NavigationPathSnapping is enabled.

  • MagneticPathSnappingFallbackThreshold

    • The threshold defining the number updates the position will stay snapped. A low count results in faster changes but less magnetism.

  • ActivateOptimisedCircularLateration

    • Calculates an estimated position based on distances to the nearest UseBestNumberOfBeacons beacons. While the default setting simply determines a weighted average (or a slightly moving "centroid"), this configuration will set up an equation system that is approximated based on the beacon-positions and their estimated distances. However, if beacons are far apart and the received RSSI values are weak, this setting can lead to worse results. A recommended beacon-density for this setting would be at least one beacon in a 3-4 metre radius.

  • ActivateEvolvingWeightedAverageLocation

    • To provide more stability, this calculate another weighted average from the last 5 calculated positions. Can cause lags if the user is moving too fast.

  • SensibleDistanceFilterSpeed

    • To avoid positions jumps, it is possible to limit the distance a position can move between two consecutive location-updates. If a position moves further than X meters per second, the remainder will be cut off. Example: If this setting is set to 2.0 meters/second and UpdatePublishInterval to 2 seconds, a newly calculated position that is originally 10 meters away will cause this filter to cap the approximation to 4 meters towards this new position, not 10. Especially if set too low, this limits possible walking speed significantly and can hence lead to lagging. To minimize lagging, an internal check dynamically increases the speed setting if lags are detected.

  • ActivateMotionDetectionFilter

    • To make the user position more stable while at rest, the motion-detector can be activated. This setting suppresses position updates while the device is standing still. To avoid false positives, the threshold for "moving" is set to a low value by default, which means that even slight movements of the device cause the calculation to start again. Note that level-changes might not be detected if the device is going up steadily on an escalator and the motion-detector is enabled. Also, while internally the very first position after startup is published regardless of this filter, this might not automatically be the very first position that is received. In a map for instance, you will want to keep this filter disabled until a position is calculated and displayed.

  • MotionDetectionDistanceFilter

    • While the motion-detector filter is enabled, it is also possible to still produce position-updates, but these will be "cut off", similar to SensibleDistanceFilterSpeed. However, since the user is detected to be standing still, this value should be reasonable low, e.g. 30cm/seconds. While this configuration is intended to show responsiveness, it might be counter-intuitive to still produce location-updates while standing still. It might also lead to a higher battery consumption.

  • ActivateBarometer

    • Use the barometer (only available in iPhone 6 (Plus) and newer) to verify if detected level-changes were correct. If a level-change is detected via beacon-signals, it is checked if in the last 20 seconds at least BarometerDefaultLevelHeight meters were covered vertically. This will use the level-numbers from Backspin, while one level has the height of BarometerDefaultLevelHeight. The filter is most useful in large open spaces with unobstructed line-by-sight to beacons of other levels. If two artificial levels are added to Backspin with the same real "altitude", this can lead to delayed level-change-detections. If you experience problems with level-changes, disable this setting entirely.

  • BarometerDefaultLevelHeight

    • The default level-height that is used for the barometer check.

  • UseBestNumberOfBeacons

    • Only use the strongest X beacons for positioning. It is not recommended to have a value greater than 5.

  • RssiPositioningThreshold

    • Filter out weakest beacons determined by this threshold before UseBestNumberOfBeacons is considered. Beacons that are too weak cannot improve accuracy.

  • UpdatePublishInterval

    • A lower frequency gives the impression of more stable results while using less battery power, yet comes at the cost of decreased responsiveness and more lagging (especially with SensibleDistanceFilterSpeed and/or EvolvingWeightedAverageLocation` enabled).

  • ActivateRssiSmoothing

    • Performs a statistical filter to smooth RSSIs based on the recent history for each beacon. Outliers and sudden fluctuations in signal strength usually do not influence accuracy significantly, but lead to delays and computational overhead. Advisable if very best accuracy is required.

  • SmartLocationUpdateRadius

    • For a more stable positioning experience, this filter suppresses the publishing of position-updates that are still in the radius of the last position. If this filter is set to 3 meters, no position will be published that is less than 3 meters away. Note that if the SensibleDistanceFilter is less than SmartLocationUpdateRadius then every 2 seconds at maximum a new position will be published.

  • BeaconTxPowerValue

    • The TX Power value defines how strong a beacon is transmitting based on a reference measurement at 1 meter. On the base of this value, distance estimations can be performed. If beacons from other manufacturers or different power settings are used, a different tx-power might need to be provided to the SDK. This setting is global. Each beacon can also be configured manually in the dashboard, which will have higher priority. If no value is provided either way, it defaults to -65 dB.

4.5. Mapview Considerations

Supplementary to the set of stability filters provided by the SDK, there are some UI adjustments that can facilitate more satisfactory user experience.

  • Position marker size

    • The blue dot used in Google and Apple Maps to display the current user position does not translate too well to indoor positioning, since the scale of the map is different. A dot with a radius of 30 centimeters leads to the impression, the accuracy for this position would be equally good, which is generally not the case. If the position-marker itself already has a radius of 3 meters however, the unjustified expectation of the user can be mitigated.

  • Smooth marker updates

    • Instead of abruptly setting the position marker to its new destination, an animated transition can help facilitate the impression of a continuous movement.

// Google Maps
[CATransaction begin];
[CATransaction setAnimationDuration:0.95f];
self.positionMarker.position = newCalculatedPosition.coordinate;
[CATransaction commit];


// Apple MapKit (http://stackoverflow.com/q/31089430)
[UIView animateWithDuration:0.95f animations:^(void){
     annotation.coordinate = newCalculatedPosition.coordinate;
  }
  completion:^(BOOL finished)completion{
     NSLog(@"Animation complete");
  }
];
  • Display live accuracy circle

    • The CLLocation objects returned from the positioning-frameworks contain a horizontalAccuracy value in meters which can be used for an accuracy circle. They are displayed around the position-marker, comparable to the ones in Google and Apple Maps.

4.6. Compass Heading / Bearing

The Backspin iOS SDK listens to compass heading updates by default. The results are published via global NSNotifications with the key kFAVCompassHeadingUpdateNotification.

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateHeadingValue:) name:kFAVCompassHeadingUpdateNotification object:nil];


- (void) didUpdateHeadingValue:(NSNotification *)headingNotification {
    self.deviceBearingToNorth = fmod(360 + ([headingNotification.userInfo[kFAVCompassHeadingValueTrueNorth] doubleValue]), 360);
    // rotate map or do something with it
}

The delegate callback shouldDisplayHeadingCalibrationPopup determines the display of the calibration overlays for the compass. Note that the compass-bearing is only available on iPhone and iPad, not iPod Touch.

5. Navigation

The navigation feature has been extended in Backspin iOS SDK v1.2. The new Swift class BSSDKNavigationTask allows targeting of multiple locations (e.g. entrances), as well as directly calculating turn-by-turn directions for each navigation update.

The list of parameters:

  • startLocation

    • The start point for the route, usually the calculated indoor-position.

  • startLocationIsCurrentPosition

    • If navigation-path-snapping is enabled during positioning, set this flag to save resources by re-using the snapping result. There is no additional input necessary, the data will be retrieved internally. If startLocation is not the calculated position from the BackspinSDK, setting this flag can have undefined results.

  • possibleTargetLocations

    • Can be used if a shop has multiple entrances (even on different levels). The framework will only return a route to the closest target. (NO traveling salesman problem)

    • During each route calculation, the shortest path to any of the locations is re-calculated, so the actual target might change.

    • If any of the locations is in proximity to the start point (on the same level and within navigationTargetProximityDistance meters), the navigation-task will be ended.

  • lookForBarrierFreeRoute

    • If barrier-freedom was defined in the KML, this flag can be used return barrier-free paths only. If no barrier-free route could be found, the returned set of route-steps is empty.

6. Notifications

Background notifications need to be allowed by the app-user:

[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound categories:nil]];
// register for remote notifications
[[UIApplication sharedApplication] registerForRemoteNotifications];

The last call is not required for local notifications but is necessary for APNS pushes (Apple Push Notifications / App Account), which also requires the corresponding UIAppDelegate method to be implemented:

- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {

	// forward the token to the Backspin server to be able to receive timed pushes
	[[BackspinSDKController sharedInstance] registerDeviceWithApnsTokenAtServer:deviceToken];


}

Since APNS-token is registered during setup, it is recommended to save said APNS-token received from Apple above and hand over when the setup is performed:

// ...

@property (strong) NSData *apnsTokenData

// ...

- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
	self.apnsTokenData = deviceToken;
}

// ...

// perform the setup with the apns-token
[[BackspinSDKController sharedInstance] setupBackspinWithApiKey:@"MY_API_KEY" andRootVenueSelectionBlock:nil andApnsTokenData:self.apnsTokenData success:nil errorHandler:nil];

6.1. Notifications and Positioning

For all internal notifications (location-based-content and beacon-proximity-notifications), beacon-ranging is required. If neither WhenInUse nor AlwaysUsage location-services permissions are granted by the user, then obviously the SDK cannot produce these notifications.

If AlwaysUsage is granted, then the notifications-module will start monitoring for proximity notifications automatically (or rather the UUID of linked proximity-beacons). For location-based notifications, positioning needs to be triggered explicitly. See also die Positioning chapter of this documentation.

6.2. Notification Delegate

To receive notifications from Backspin SDK, implement the corresponding delegate method:

- (void)handleAppNotification:(BSAppNotificationModel *)appNotificationModel {
	...
}

This delegate method uses the BSAppNotificationModel with the following properties (Source comments omitted here, please refer to the public header file):

@interface BSAppNotificationModel : NSObject
@property (readonly) NSString *notificationTitle NS_AVAILABLE_IOS(8_0);
@property (readonly) NSString *notificationBody;
@property (readonly) NSArray *actions;
@property (readonly) NSArray *notificationKeyValueData;
@end

The notificationKeyValueData property contains all the custom key-value pairs that were defined for this notification in the Backspin Dashboard, for instance:

[ {"key": "foo1", "value": "bar1"}, {"key": "foo2", "value": "bar2"}, {"key": "foo3", "value": "bar3"} ]

The actions-array contains actions that are associated with this notification, mainly to be interpreted and executed by the Backspin iOS SDK itself.

If you want the actions to be executed, forward the model to the SDK: [[BackspinSDKController sharedInstance] executeAppNotification:appNotificationModel];

At the moment, two action types are available:

  • Popup

  • Detailview

6.3. Provide custom detailviews

If a detailview (e.g. for an offer or a venue/shop) shall be displayed as a result of a notification that is executed via executeAppNotification:, a module-interface needs to be provided, as described here: How to create Custom Views through BackspinSDK Push Service

6.4. BackspinSDKControllerDatasource

The Backspin iOS SDK automatically searches for the Localizable.string file to localise any popups. If you don’t use the Localizable.string file you should implement the BackspinSDKControllerDatasource protocol and return your own custom translations.

The following keys exist and should be translated:

locationmanager_ls_disabled_title locationmanager_ls_disabled_text

Notify the app-user that location-services are disabled

locationmanager_ls_unauthorised_title locationmanager_ls_unauthorised_text

Notify the app-user that the app is not authorised to use location-services

locationmanager_ble_unavailable_title locationmanager_ble_unavailable_text

Notify the app-user that bluetooth is disabled

locationmanager_popup_ok

Text for the “OK” button of any popup with text as above

locationmanager_popup_gotosettings

Text for a button to go directly to location-services-settings

notification_popup_title_offernearyou

Title for a offer-notification

notification_popup_doyouwanttoseedetailsforoffer

Text for a offer-notification, the title of the offer will be following this text after a whitespace

notification_popup_title_shopvenuenearyou

Title for a shop-notification

notification_popup_doyouwanttoseedetailsforshopvenue

Text for a shop-notification, the title of the shop will be following this text after a whitespace

notification_popup_ok

Text for the “OK” button for a notification-popup, to close a pure “message” popup with no further action

notification_popup_cancel

Text for the “Cancel” button for a notification-popup (e.g. to cancel the opening of a detail-view)

notification_popup_continue

Text for the confirmation-button to open up a detail-view

core_update_nointernetconnection_title core_update_nointernetconnection_text

Alert-View that no internet connection is available on first start and at subsequent starts until an initial internet connection has been established

core_update_longtimenoupdate_title core_update_longtimenoupdate_text

Alert-View that the last content-update was over two weeks ago

core_update_close

“Close” button for the two alert-views above

Without a Localizable.strings, implement the datasource method:

- (NSString *)localisedStringFor:(NSString *)localisationKey {

    NSString *localisedString = localisationKey;

    if ([localisationKey isEqualToString:@"notification_popup_title_offernearyou"]) {

    } else if ([localisationKey isEqualToString:@"notification_popup_doyouwanttoseedetailsforoffer"]) {

    }  else {
	// ...
    }
    return localisedString;
}

- (NSString *) currentLanguageDesignator {
    return @"de";
}

7. Model-Store & Server API

As described in List of Functions, the Backspin iOS SDK contains an API to download, parse, and cache models. The following are some further remarks to help you using the API and make design-choices.

In general, the model-store as well as the models are read-only, so in case it is required to change a model locally, it would be best to use your own solution.

7.1. Model Request

A short example on how the workflow should look like:

BSBackendConnectionDownloadRestEndpoint *restEndpoint =
    [BSBackendConnectionDownloadRestEndpoint endPointWithLanguage:@"de" andRootScopeId:23];

// add objects of interest, see next section for more details
...

[[BackspinSDKController sharedInstance] requestModelDownloadOrUpdateForRestEndpoint:restEndpoint
      andCompletionBlock:^(BSBackendConnectionDownloadRestEndpoint * _Nonnull restEndpoint,
                         NSDate * _Nonnull lastServerUpdate) {


    NSArray *allVenueModels = [BSSDKModelStore modelsForType:@"Venue" withFilters:nil andRootScopeId:-1];

    // only load venues from root-scope 23
    NSArray *venueModelsForCurrentScope = [BSSDKModelStore modelsForType:@"Venue" withFilters:nil andRootScopeId:23];

    // load all venue-models from all root-scopes, but only shops
    NSArray *venueModelsWithFilter = [BSSDKModelStore modelsForType:@"Venue" withFilters:@[
    		@{@"name": @"venueType", @"equals": @"shop"}]];
}];

7.2. Objects of Interest

Important
While the model-download API is mostly identical in version 1.1 and 1.2, the handling is different. If you were using the API in version 1.1 already, read this section especially carefully. If not, it is still highly advisable.

The REST v2 API (introduced in SDK v1.2) is designed to be less "chatty", thus reducing the amount of REST calls for each client. Instead of separate endpoints for each model-type, there is now one global endpoint for all models. Since not all model-types might be required, the amount of data downloaded can be reduced with exclusion filters or a positive list of "objects of interest".

To reduce the number of requests, the SDK itself will not perform any updates on its own. It is advisable to call triggerContentUpdateFromServer inside the completion-block of the initial setup, which will download the minimum set of model-types:

  • Beacon

  • Level

  • NavigationGraph

  • NotificationConfig

Additionally, three (root) model-types can be requested. For each one of these, a set of exclusions can be defined for unwanted properties (at the root-level)

  • Venue

  • VenueCategory

  • VenueOffer

Note
For each root-venue / root-scope only ONE request needs to be sent. All models for a specific root-scope are saved in one language at a time only. If the language for a root-scope request changes or anything in the objectsOfInterest set is different, all models are downloaded again.

To complete the source-example from the prior section to request an extended set of model-types:

BSBackendConnectionDownloadRestEndpoint *restEndpoint =
[BSBackendConnectionDownloadRestEndpoint endPointWithLanguage:@"de" andRootScopeId:23];

BSBackendConnectionDownloadObjectOfInterest *objectOfInterest = [BSBackendConnectionDownloadObjectOfInterest new];
objectOfInterest.rootModelDTO = [BSGenericModelSQLite modelTypeVenue];
// do not include the "venueLocations" property for a venue-model
objectOfInterest.propertiesToBeExcluded = @[@"venueLocations"];

restEndpoint.objectsOfInterest = @[objectOfInterest];
[[BackspinSDKController sharedInstance] requestModelDownloadOrUpdateForRestEndpoint:restEndpoint andCompletionBlock:nil];

In this request, besides Beacon, Level, NavigationGraph, and NotificationConfig model-types, all Venue models will be downloaded but without their venueLocations property. All models would be downloaded in German (de) and for the root-venue with scope-id 23.

You may reuse the objectsOfInterest set for other rootscope requests as well.

If you require a custom model-type to be loaded/updated, you must use requestModelDownloadOrUpdateForRestEndpoint every time rather than triggerContentUpdateFromServer

Important
One remark: While it is possible to specifiy excluded properties for each root-model-type, also the "internal" ones like Beacon, NavigationGraph, etc. it should be handled with caution. If a property is excluded that is required, then the SDK might not work as expected any more. You may consult with favendo if you can safely exclude a specific property on an "internal" model-type. One property you can safely exclude for all model-types is modifiedAt which is not required on per-model-basis.

7.3. Model Store

All models downloaded from Backspin are persisted as JSON, so the only model-object that is available is BSGenericModelSQLite which offers generic accessors according to object-type (e.g. stringForName:). All nested/linked models will be replaced with generic-model-instances automatically, e.g. the linked VenueCategory models from within a Venue model:

#import <BackspinSDK/BSModelVenue.h>
#import <BackspinSDK/BSModelVenueCategory.h>

...

NSArray *venueModelsWithFilter = [BSSDKModelStore modelsForType:
	[BSGenericModelSQLite modelTypeVenue]
	withFilters:@[
    		@{@"name": @"venueType", @"equals": @"shop"}]
   ];

BSGenericModelSQLite *oneShopModel = venueModelsWithFilter.firstObject;
NSString *shopName = [oneShop venueName];

// references models with Type "VenueCategory" from REST endpoint /venuecategories
NSArray *shopCategories = [oneShopModel venueCategories];
BSGenericModelSQLite *firstShopCategory = shopCategories.firstObject;
NSString *shopCategoryDescription = [firstShopCategory descriptionString];

The SDK provides a set of Objective-C categories for BSGenericModelSQLite models returned by the rootscopedata REST endpoint:

  • BSModelBeacon.h

  • BSModelLevel.h

  • BSModelNavigationGraph.h

  • BSModelNotificationConfig.h

  • BSModelVenue.h

  • BSModelVenueCategory.h

  • BSModelVenueOffer.h

In order to save client and server resources, an update-operation is only performed at most every 30 minutes. For an instant update-operation, call resetServerUpdateTimestampsAndTriggerModelDownloadOrUpdateForRestEndpoint:andCompletionBlock: While the update-operation is in progress, all existing models remain available.

Note
An existing BSGenericModelSQLite obtained from the model-store will not change with an update-operation. If access to the newest data is imperative, reload all models once the BackspinSDKRestEndpointModelRequestCompletionBlock is called.

7.4. Additional Server API

Particularly for the User Accounts API, it is advisable to download and upload plain JSON data:

  • sendJsonObject:toPath:withHTTPMethod:

    • Send a request to Backspin with the given HTTP method. Every HTTP-method is possible. The block contains the server-return-value as deserialised JSON object as NSDictionary or NSArray.

  • GETJsonObjectFromPath:

    • Request a plain JSON from the given REST endpoint, deserialised into a NSDictionary or NSArray.

  • POSTJsonObject:toPath:

    • Convenience method to send a POST request to Backspin

  • PUTJsonObject:toPath:

    • Convenience method to send a PUT request to Backspin

8. Analytics

A list of the out of the box analytics event types can be found at Analytics. Additionally, custom analytics events can be queued and sent to the server.

For custom events, the BSSDKAnalyticsTypeHandler protocol is to be implemented, which modifies the data to an analytics-event.

Add your implementation to the set of handlers via [[BackspinSDKController sharedInstance] addAnalyticsTypeHandlerIfNotAlreadyAdded:] The handler will be called for every analytics-event that is about to be sent to the Backspin server, so make sure to check for the currentEventType if it is relevant for you.

By default, an event has the following values:

{
	"type": "THE_TYPE",
    "timestamp": 1441283129969,
    "timezone": "Europe/Berlin",
    "timeoffset": "7200",
    "platform": {
        "device": "iPod Touch 6th Gen",
        "platform": "iPhone OS",
        "platformVersion": "8.4.1"
    },
    "payload": {
    	"rootScopeId": ROOTSCOPE,
    	"appAccountId": HASHED_APP_ACCOUNT_ID,
    	"userAccountId": HASHED_USER_ACCOUNT_ID
    }
}

The account-ids will be hashed, if enableAnalyticsPrivacyMode is set. Otherwise the plain app-account-id and user-account-id will be used.

All other values need to be provided by the BSSDKAnalyticsTypeHandler. You should put your data in payload.

This is an example of an internal analytics-manager for indoor-location events:

@implementation BSLocationAnalyticsManager

- (BOOL) respondsToEventType:(NSString *)currentEventType {
    return [currentEventType isEqualToString:@"location_updated"];
}

- (void) addFieldsToJsonObject:(NSMutableDictionary *)jsonObject forEventType:(NSString *)currentEventType andData:(NSDictionary *)analyticsData {

    if([self respondsToEventType:currentEventType]) {

        jsonObject[@"location"] = @{
                                    @"latitude": analyticsData[@"latitude"],
                                    @"longitude": analyticsData[@"longitude"]
                                    };
        jsonObject[@"payload"] = @{@"floor": analyticsData[@"floor"], @"isIndoors": analyticsData[@"isIndoors"]};
    }
}

@end

You can put any information into the payload. These analytics-event might not show up in the Backspin dashboard, however.

Note
If your custom analytics-event is related to an indoor-location, you might want to add the location-key with the latitude and longitude to your generated JSON object, as well as the keys floor and isIndoors in the payload.

9. Troubleshooting / FAQ

9.1. libswiftCore.dylib

If you cannot compile the project, some additional flags might need to be set. First, make sure all flags are set as described in Adjust Project Settings.

dyld: Library not loaded: @rpath/libswiftCore.dylib
This error appears directly after start of the app:
dyld: Library not loaded: @rpath/libswiftCore.dylib

If this error shows up after starting the app, make sure that the Build-Setting flag “Embedded Content Contains Swift Code” is set to “Yes

9.2. Notes about iOS Background Modes

During the review process, Apple might take a second look if an app requires any kind of background activity as indicated by settings in the apps Info.plist.

Since iOS 9, suspended apps that want to work with the CLLocationManager in any way need a "Location Updates" background mode flag, which is a permission for GPS as well as beacon updates. However, even if an app uses beacon related activity in background-mode, Apple wants to make sure this use is justified. As generally the case in App Store Reviews, it will be Apple deciding whether your reasons meet their standards of justifiability.

Remember that even without the "Location Updates" background mode, the app will be woken up with monitoring and can performs tasks for 10 seconds. Proximity events triggered by smartly placed beacons usually suffice.

Apple does allow background mode for "Navigation" apps which provide continuous turn-by-turn directions in the background. Such apps usually provide speech output for the next direction while in background.

9.3. Do I really need "AlwaysUsage"? This might scare app-users.

Apple wants to make sure that the privacy of app-end-users is a high priority, while at the same time use battery power as conservatively as possible. Any kind of background activity, especially in combination with the users location, is not taken lightly - which is one reason why the AlertView text sounds so "scaring".

Allow "App X" to access your location even when you are not using the app?

Unfortunately, beacon monitoring does not work - neither in background nor in foreground - if the AlwaysUsage permission is not granted. Positioning and beacon ranging can still be started "manually" though.

You do not need the AlwaysUsage permission if you do not perform anything in the background - as described in detail above. But if AlwaysUsage is enabled and granted by the user, even without Background Mode, it is still possible to wake up the app for about 10 seconds even by monitoring. If you do not intend to publish a location-based or beacon-proximity notification while the app is in background (or any notification that is related to entering a beacon-region), the WhenInUse permission suffices. Likewise, if you merely display a map with a user’s position and stop any Beacon-related activities if the map is not visible, neither a Background Mode nor the AlwaysUsage permissions are required.

9.4. Positioning / Beacon ranging does not start

Make sure that Bluetooth is enabled on the device and a permission has been granted for Location Services, either AlwaysUsage or WhenInUse.

The positioning will NOT start if monitoring is used and the AlwaysUsage permission has not been granted by the app-user, even if the app is in foreground.

If the user has just revoked the permission or disabled Bluetooth, the monitoring will start (again) as soon as the AlwaysUsage permission is given again and Bluetooth is enabled.

With WhenInUse permissions, the positioning/ranging has to be started "manually":

// start positioning/ranging NOW, regardless if beacons are around or not
// only requires the "WhenInUse" permission
[[BackspinSDKController sharedInstance] startIndoorPositioningWithBeaconUUID:"UUID" cryptoBeacons:false];

In this case positioning will stop once the app goes in to background mode.

It is also possible that your data is not available yet. Since with version 1.2, no data is downloaded automatically, you will need to call triggerContentUpdateFromServer or requestModelDownloadOrUpdateForRestEndpoint: before positioning, navigation, or notifications can be used. See also Model-Store & Server API for the API to request models from the server.

Make sure that the beacons are configured for "Navigation" in the dashboard and the correct root-scope is selected. A useful indicator for incorrect data is to check whether beacons are ranged at all (e.g. if the global kFAVBeaconsRangedNotification notification is fired).

See also The setup and/or data download takes too long if you can see ranged beacons, yet still do not receive notifications or a position.

9.5. No valid navigation route is returned

It is possible that your data is not available yet. Since with version 1.2, no data is downloaded automatically, you will need to call triggerContentUpdateFromServer or requestModelDownloadOrUpdateForRestEndpoint: before positioning, navigation, or notifications can be used. See also Model-Store & Server API for the API to request models from the server. See also the next section for timeouts.

Make sure that a valid path actually exists. If you defined a KML with directed paths, there should be at least one route in the desired direction that is passable. Likewise, if barrier-free navigation is enabled, there should be at least one barrier-free path.

9.6. The setup and/or data download takes too long

There are multiple reasons why the setup-block is not called or with a high delay. Usually, either the Backspin server is offline/not reachable or the device has no sufficient internet connection.

If this is the first time the setup-method is called, the SDK will render functionless, since it needs to initially register at the server and download data. The SDK will wait until it finds the Backspin server reachable (see Apple Reachability for iOS). Only if the reachability status changes, an request-attempt is made. After the initial download, all data is available offline. If the reachability status indicates that the Backspin server is reachable, an update request might be attempted.

If cellular data is available but no data can be received (e.g. exhausted mobile data plan), iOS will determine the device to be online and the SDK will try to perform data updates if the SDK-data has "expired", so the SDK callbacks are called with a delay until the requests time-out. Custom timeouts can be set in order for the update-requests to terminate faster. There are three configurable network timeouts in the SDK:

requestTimeoutForAccountRegistration An account is created at the very first start of the SDK and update whenever a new APNS-token is provided. If it is not the first start any more, the setup will return immediately and use offline-data.

Only after the account-request returns, the setup is continued. By default, the account-request times out after 10 seconds

requestTimeoutForRootVenues After the account data has been loaded (from server or the offline storage), the root-venues are updated. An update is only performed every half an hour and only if the Backspin server reachability status is Reachable. Only if the setup will continue/complete, the root-venue-request returns. By default, the root-venue-request times out after 10 seconds.

requestTimeoutForOtherRequests Used fo all other requests.

In the worst-case scenario where all consecutive timeouts are triggered, it can take up to 20 seconds until the setup is completed and another 10 seconds for the SDK is to start loading data (if prior setup was successful).