List of Functions

Provided by the iOS SDK

Positioning

The SDK offers beacon based indoor-positioning including bluloc-exclusive crypto-beacon (rolling-id mode) support. Since iOS SDK 1.1 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).

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.

  • Unless the Background-Mode for Locations is enabled in the app/Xcode, the app will resign once it is not in the foreground any more. You will get around 10s of activiy if a beacon-region is entered (or exited), but for anything else, the background-mode is required. Since BackspinSDK v3.1.0, there is an extended background-mode using GPS, see also Can I receive SDK notifications and position updates in the background?

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 (see Backspin dashboard documentation, 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.

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.

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 vary with each setup and beacon.

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

  • A location based notification can be configured in the dashboard as a venue or offer based notification.

  • 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.

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.

Apple Push Notifications / App Account

Backspin sends push notifications via the Apple Push Notification Services (APNS). As explained in the Apple Documentation (or e.g. at Ray Wenderlich), you need to enable the service in the Apple Member Center, upload the certificate to the Backspin server (to do this, please contact your favendo project manager. He’s gonna do that for you) 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:

While in Backspin iOS SDK v1.1 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.
Since REST v2 / Backspin iOS SDK v2.0 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, likes, or leaflet. As of now these two accounts have the same ID. See [rest-user-accounts]

User Account

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.

It is used to hold user-specific data like a likes-list or account-profile. See more at User Account.

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
    is created automatically
    internal key location_updated

  • Notification created
    is created automatically, right before the notification-object is handed out to the SDK-user
    internal key notification_created

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. See section Analytics in the dashboard manual on how to see these events in the Backspin dashboard.

Note that analytics are disabled by default, it needs to be enabled via [BackspinSDKController sharedInstance].analyticsEnabled. If enabled, all types as listed above are enabled, but can be disabled separately.

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 [rest-user-accounts].

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 “Always Embed Swift Standard Libraries” 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.com/Backspin-iOS-SDK/Backspin-iOS-SDK_3.4.1.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!
This setting might affect other pods.

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

External Dependencies

The Backspin iOS SDK has one external framework dependency that needs to be added to your project manually:

The SDK is linked with the following version of this framework:

  • AFNetworking: 3.1.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.

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 “Always Embed Swift Standard Libraries” to “Yes

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

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

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

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.

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

Enable Motion Sensor

With iOS 10 it is required to insert the NSMotionUsageDescription key in the app’s Info.plist: NSMotionUsageDescription

Important: To protect user privacy, an iOS app linked on or after iOS 10.0, and which accesses the device’s accelerometer, must statically declare the intent to do so. Include the NSMotionUsageDescription key in your app’s Info.plist file and provide a purpose string for this key. If your app attempts to access the device’s accelerometer without a corresponding purpose string, your app exits.

Logging with CocoaLumberjack

Only relevant for long-time users: As of release Backspin iOS SDK 2.1.3 there is no dependency to CocoaLumberjack any more and no special handling is required.

Custom Region Monitoring

If you 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.

Start Using the Backspin iOS SDK

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>

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).

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.

Since version Backspin iOS SDK v2.0 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"];

// use either BSSDKServerInstanceUAT or BSSDKServerInstancePROD, depending where your backspin account is created
[[BackspinSDKController sharedInstance] setupBackspinWithBasicAuth:@"YOUR_BASIC_AUTH_KEY" forServer:BSSDKServerInstanceUAT 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.

Positioning

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.

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.
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)
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.

Monitoring

Since iOS 7.1, beacon region monitoring is reliable and responsive enough to be preferable to manually starting 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, 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 while in background. After that time, the App will be shut down again, unless background modes for location are enabled. 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
// read out the UUIDs and crypto-config from the beacons in the local database once content has been loaded from Backspin
[[BackspinSDKController sharedInstance] startMonitoringBackspinPositioningBeacons];

// start positioning/ranging NOW, regardless if beacons are around or not
// only requires the "WhenInUse" permission
// read out the UUIDs and crypto-config from the beacons in the local database once content has been loaded from Backspin
[[BackspinSDKController sharedInstance] startIndoorPositioningWithBackspinPositioningBeacons];

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

// this will stop monitoring of all regions
// if currently inside an active beacon-region and ranging is going on, this will be stopped immediately
[[BackspinSDKController sharedInstance] stopMonitoringOfBackspinPositioningBeacons];

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

Accuracy Settings

There are three settings presets which bundle a number of specific configurations to optimise 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.

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 additional factor is the provided KML.) Your favourite favendo contact person will be happy to assist you with choosing a good setting preset for your location.

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];
	}
...
}

Positioning Configurations

These settings are explained in the BSSDKPositioningConfiguration enum which can be found in the header-file 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 that point, the accuracy-presets can be activated as well.

  • NavigationPathSnapping
    Snaps the indoor-location to the nearest navigation-path to make the position marker more stable. This feature 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 (see dashboard documentation) 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.

  • ObstacleChecking
    Prevents the position moves through obstacles. Obstacles can be defined with different strengths, defining how many position-updates are necessary for a fallback to trigger. Is an advanced alternative to magnetic path snapping. Also, keep in mind that this feature only works if a KML (see dashboard documentation) has been provided. Enabling this configuration without a KML will render effectless.

  • MagneticPathSnapping
    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.

  • MagneticPathSnappingDistanceThreshold
    Should represent the distance between two "parallel" paths. If they are farer apart then this threshold, magnetic path snapping will not have any effect. Can also be dynamically changed on runtime if the environment changes (e.g. a different level with paths farer apart). The default is 3m, which represents an average aisle-width in a supermarket.

  • OptimisedCircularLateration
    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-4m radius.

  • EvolvingWeightedAverageLocation
    To provide more stability, this calculates another weighted average from the last X calculated positions. See next option. Can cause lags if the user is moving too fast.

  • EvolvingWeightedAverageLocationHistorySize
    Define the number of location-updates from the history that should be considered for weighted averaging. Each second a new location is calculated (if the updateFrequency is HIGH).

  • MotionDetectionFilter
    To make the user position more stable while at rest, the motion-detector can be activated. Prior to BackspinSDK version 3.3.1, this setting would completely suppress position updates, since v3.3.1 a mode is activated which results in a very strong smoothing while the user is detected not to be walking. The position will therefore be very stable and not jump suddenly. However, the position can still change over time, but very slowly. Note that level-changes might be detected with a delay that is higher than usual, e.g. while standing on an escalator without moving.
    This setting will change some other settings if the user is detected to be not walking:

    • .activateRssiSmoothing

    • .rssiSmoothingHistorySize

    • .activateEvolvingWeightedAverageLocation

    • .evolvingWeightedAverageLocationHistorySize

    • .activateBarometer

  • 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. A fast average walking speed is 1.7m/s, so the default preset of 2.5m/s should fit most needs.

  • MotionDetectionDistanceFilter
    Prior to BackspinSDK version 3.3.1, the motion-detector would completely suppress position-updates, this distance-filter could ensure, however, that small updates are still published - to prevent that the position would freeze at a "wrong" coordinate. With the new strong smoothing, this problem is circumvented, so this feature should not be necessary. It can still be set to avoid big jumps while standing. See also SensibleDistanceFilterSpeed - if this value is set to a value greater than 0, it is used instead of SensibleDistanceFilterSpeed while the user is detected to be standing.

  • Barometer
    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
    The interval in which position updates are published. Since BackspinSDK v3.3.1 the position is still calculated every second, but might not be published that often. This results in a more smooth and stable experience for the user.

  • RssiSmoothing
    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. The history-size can also be set to create a smoother result, but this can also increase the delay of updates.

  • 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 at maximum every 2 seconds a new position will be published.

  • SensibleRssiFactor
    Filter out weak beacons relative to the strongest beacon. The factor defines how much weaker the RSSI of beacons can be before they are filtered out. E.g. if the strongest beacon has an RSSI of -65 dB and the factor is 1.2, then the weakest beacon that is allowed would have an RSSI of -78 dB.

  • 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.

  • AdaptivePositioning
    As an advanced measure for more stable positions, adaptive positioning can be enabled, which is a newer alternative to the MotionDetector setting. A walking-detector is used to determine if the user is standing or walking. If the user is standing for extended periods (some seconds), several other settings are raised to increase stableness. In a sense, adaptive positioning is a "combined" setting for several others that change during runtime.

    These other settings include:

    • .activateMotionDetectionFilter

    • .activateRssiSmoothing

    • .rssiSmoothingHistorySize

    • .activateEvolvingWeightedAverageLocation

    • .evolvingWeightedAverageLocationHistorySize

    • .smartLocationUpdateRadius

    • .activateBarometer

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.

Position Marker with Accuracy Circle

Position Marker as Blue Dot

See the mapview example with a blue dot marker and a bigger "accuracy" marker, which also has an accuracy circle around it.

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 {
	double deviceBearingToNorth = fmod(360 + ([headingNotification.userInfo[kFAVCompassHeadingValueTrueNorth] doubleValue]), 360);

	// rotate map or do something with it
	mapMarker.rotation = deviceBearingToNorth;
}

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.

Navigation

The navigation feature has been extended in Backspin iOS SDK v2.0. 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 (ask your favendo project manager), 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.

User Account

Currently a user account is created automatically during the setup. In future versions it might also be possible to let the user login (and logout), thus allowing the user to change or share an account over multiple devices.

The user account handled by the Backspin iOS SDK currently has two features: An offer leaflet and a likes list. The offer-leaflet is used to manage gathered venue-offers (Backspin model type VenueOffer), while the likes-list can be used to like multiple models (e.g. Venue, VenueOffer, Beacon, LevelPlan).

If you want to use these features, you obviously need to work with the internal Backspin models, it is recommended to also take a deeper look at Model-Store & Server API.

There are a number of calls provided to handle the management of the leaflet and likes list:

You can retrieve all items from the leaflet and likes as dictionaries. Both have an "id" key with integers values. Likes have an additional "type" key with the model-type (Venue, VenueOffer, etc.) as possible values:

- (nullable NSArray<NSDictionary *> *) getLikes;
- (nullable NSArray *) getLeaflet;
Both methods are also available with a "Models" suffix, returning arrays of the actual Venue or VenueOffer BSGenericModelSQLite objects.

To post an entire new likes array or leaflet with the above defined dictionaries:

- (void) updateLikes:(nonnull NSArray<NSDictionary *> *)likes;
- (void) updateLeaflet: (nonnull NSArray *)leaflet;

For convenience, you can simply toggle the status of a like item by passing a single dictionary with the two above defined keys or a leaflet item by passing its venue offer identification:

- (void) toggleLike: (nonnull NSDictionary *)like;
- (void) toggleLeafletItem:(NSInteger)leafletItemID;
The toggleLike method is also available with a "Models" suffix, taking a BSGenericModelSQLite object.

Profile Config

For each app a profile-config needs to be set up. Contact your favendo project manager to set up an account profile for your needs. Then each user can access her own profile via the SDK.

There are generic wrappers that offer manipulating the profile

- (void) requestAccountProfileFields:(BackspinSDKAccountProfileLoadedSuccessBlock _Nullable)completionBlock;
- (void) updateAccountProfileOnServer;

The first method will return an array of profile-fields, according to the created profile-config in the Backspin dashboard.

There are four types of fields supported at the moment:

  • BSSDKAccountProfileDomainField

  • BSSDKAccountProfileFreeTextSingleField

  • BSSDKAccountProfileFreeTextMultiField

  • BSSDKAccountProfileFreeTextRangeField

Notifications

Notifications need to be allowed by the app-user:

[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound categories:nil]];

...

- (void) application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
    if(notificationSettings.types != UIUserNotificationTypeNone) {
		// register for remote notifications (you have to register an APNS certificate at developer.apple.com)
		[[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
	// only AFTER the setup-completion-block has been called
    if([BackspinSDKController sharedInstance].sdkState == BSSDKInitialisingStateInitialised)
        [[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];

Hint: Make sure to also implement the error-callback in the application-delegate for debugging purposes.

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;

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.

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;

/** the linked notification-config model. See BSModelNotificationConfig.h */
@property (readonly) BSGenericModelSQLite *notificationModel;

/** number of times this notification has been executed (including the current execution) */
@property (readonly) NSInteger executionCount;
@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 notificationModel links the notification-config which has more information about the notification.

While the notificationModel contains all beacons / zones that are linked, there is currently no information which beacon/zone actually triggered the notification.

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

Provide custom detailviews

If a detailview (e.g. for an offer or a venue/shop) ought to be displayed as a result of a notification that is executed via executeAppNotification:, the BackspinSDK delegate will be informed via displayDetailViewForModelWithModelType:andId:. In there you can retrieve the model from the BSSDKModelStore and present a detail-view of your liking.

In BackspinSDK < 3.2.0 the creation of view-controllers for models was done internally which required the delegate-method moduleNameForViewType and the implementation of two protocols BSSDKModule and BSSDKModuleUI. These dependencies have been removed for the more flexible displayDetailViewForModelWithModelType:andId:. The delegate-method has been removed, the protocols still exist (for now) but are not used any more and have been marked deprecated.

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";
}

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.

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"}]];
}];

Objects of Interest

While the model-download API is mostly identical in Backspin iOS SDK version 1.1 and 2.0, 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 v2.0) 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, these (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)

  • Coupon

  • CouponCategory

  • Venue

  • VenueCategory

  • VenueOffer

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.

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.

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"}]
    andRootScopeId:kBSSDKModelStoreUseCurrentRootScopeId
];

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:

  • BSModelAssetTracking.h

  • BSModelBeacon.h

  • BSModelCoupons.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 15 minutes. For an instant update-operation, call resetServerUpdateTimestampsAndTrigger
ModelDownloadOrUpdateForRestEndpoint:andCompletionBlock: While the update-operation is in progress, all existing models remain available.

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.

Additional Server API

To manually send requests to the server, the following methods are available: with plain JSON requests/responses:

  • 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

Asset Tracking

The SDK offers a polling API for asset-tracking updates, the API is documented in BackspinSDKController+AssetTracking.h

If authentication is required, you need to provide a user-token to the SDK before requesting any assets, as otherwise you might not have sufficient permissions to read asset-data, see setAssetTrackingUserToken:.

There are five model types for asset-tracking:

  • Asset
    The main model, this defines an asset that is tracked.

  • AssetPosition
    The position for a given asset. This is in world-coordinates (latitude/longitude) with a level-number and accuracy. It also references all zones that this position lies in.

  • AssetZone
    A geo-polygon in which an asset can be in. It has a name, level, and corners as latitude/longitude list.

  • AssetZoneAlarm
    A zone-alarm object defines when to trigger a zone-alert for an asset, i.e. this is the "configuration" for future alerts. It defines a list of zones and if an alert should be triggered if the asset enters or exists these zones. Each zone-alarm can be activated or deactived.

  • AssetZoneAlert An alert is triggered if an asset enters or exists a zone that is defined in a zone-alarm.

Furthermore, the API has (up to) three parts for each request: Performing an asynchronous server-request, updating information for existing data, and retrieving cached data from the local model-store.

For example for assets there are three methods:

  • loadAssetTrackingAssetsAsynchronously
    This will perform a server-request to load asset-information (including their asset-positions). There is no caching involved, each method-call will result in a http-request to the server.

  • updateAssetTrackingAssetPositionsForIds
    After the first method has been called, this method can be used to just update the asset-position, since the asset-information is unlikely to change. Also, a set of asset-IDs can be defined that are to be updated. This method should be used for background updates, as there will be less data downloaded compared to loadAssetTrackingAssetsAsynchronously

  • getAssetTrackingAssets
    This call will retrieve existing models from the local modelstore. If no assets have been downloaded yet, this will return an empty list.

There are also two methods to help retrieving assets that are identified by an external ID:

  • loadAssetTrackingAssetsAsynchonouslyByExternalIds:

  • getAssetByExternalId:

There will return/retrieve asset-models. For further handling, use their respective modelID (which is different to their externalId property).

Continuous Updates

One can register for continuous updates for asset-positions and zone-alerts. There is a simple API with a block/closure that is called whenever an update is received from the Backspin server:

  • startWatchingAssetPositions:
    Start watching asset positions. The block is called whenever an asset-update is received.

  • stopWatchingAssetPositions
    Stop watching asset positions.

  • startWatchingAssetZoneAlerts:
    Start watching asset zone alerts. The block is called whenever an alert is triggered in the backend.

  • stopWatchingAssetZoneAlerts Stop watching asset zone alerts.

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": "ANALYTICS_EVENT_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 rootScopeId field will not be added, if no rootvenue/rootscope has been selected (e.g. if the SDK was setup via setupBackspinWithoutRootVenueSelectionWithBasicAuth).

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 {
	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. That additional data might not show up in the Backspin dashboard, however.

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. The current root-scope-id, app-account, and user-account are added automatically. By default the app-account and user-account are hashed to protect the users privacy.
Internally each analytics-handler has to be a unique class. If you want to handle to different analytics-events, perform "analytics-type"-checking internally and add only 1 instance of your BSSDKAnalyticsTypeHandler or create two different classes. Otherwise only the first instance of a class will be added as handler!

Troubleshooting / FAQ

Can the SDK size be reduced?

By default, the app contains slices for both the device (arm) and the simulator (x86_64/i386). Unfortunately, Xcode does not strip out unneeded slices from frameworks if the app is compiled/archived.

Luckily, the SDK contains a script which strips unneeded slices. Go to your target app in the project view and "Build Phases". After the "Embed Frameworks" (or CocoaPods copy frameworks) build phase, insert a new "Run Script Build Phase" with the following content:

sh "${SRCROOT}/PATH_TO_BACKSPIN_SDK/strip_backspin_slices.sh" "${EFFECTIVE_PLATFORM_NAME}" "${CONFIGURATION_BUILD_DIR}" "${FRAMEWORKS_FOLDER_PATH}" "${EXPANDED_CODE_SIGN_IDENTITY}"
the script moved from inside the BackspinSDK.framework to its parent directory in v3.1.0!

Adapt the path, the rest can be left untouched - these are Xcode environment variables. You may look into the script for more information.

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 “Always Embed Swift Standard Libraries” is set to “Yes”.

The app crashes on iOS 10

If indoor-positioning is used with active motion-detector, iOS 10 requires a small change to the app. In iOS 10 the user privacy concept got a little bump, so now it is required to ask for permission if using the device’s' accelerometer. Make sure to insert the key NSMotionUsageDescription in the app’s Info.plist: NSMotionUsageDescription

Can the SDK be initialised without root-scope?

You may use the setup-method setupBackspinWithoutRootVenueSelectionWithBasicAuth (see BackspinSDKController+ExtendedServers.h) to setup the SDK with an "invalid" rootscope id. The functionality of the SDK is mostly restricted to queuing analytics-events, as other functionality requires available data (which is linked to a root-scope), like notifications, positioning, navigation.

It is still possible to set a root-scope after the setup:

...

// get a list of all root-venue ids
[[BackspinSDKController sharedInstance] availableRootScopesIds:^(NSArray<NSNumber *>* _Nonnull rootScopeIds) {
    NSNumber *firstRootScope = rootScopeIds.firstObject;
    [[BackspinSDKController sharedInstance] setRootScopeId:firstRootScope.integerValue];
}];

It is also possible to retrieve more data about the root-venues, if needed. The Root-Venue models can be retrieved from the database and iterated:

// check which root-scope should be loaded
NSArray *rootVenueList = [BSSDKModelStore modelsForType:@"RootVenue" withFilters:nil andRootScopeId:-1];

for(BSGenericModelSQLite *rootVenue in rootVenueList) {
    // a root-venue is still a regular venue for the most part, so you may use everything from BSModelVenue.h
    if([[rootVenue venueName] isEqualToString:@"MyVenue"]) {
        [[BackspinSDKController sharedInstance] setRootScopeId:[rootVenue integerForName:@"ownScopeId"]]
    }
}

Can I receive SDK notifications and position updates in the background?

Since BackspinSDK v3.1.0 the background-ranging capabilities have been extended, to continue ranging for beacons while the app is not in foreground-mode. For that, the Location background-mode is used that needs to be set for the target app in Xcode.

Once a beacon-region is entered, "GPS" updates are started, as long as the app receives "GPS" updates, the SDK also continues to range beacons. If the last beacon-region is exited, the GPS-updates are also stopped, to conserve battery and to comply to Apple’s App Store Review Guidelines.

Depending on the use-case it might be a good idea to reduce the ranging-frequency in the background, or set a positioning-configuration with less computational overhead (e.g. the PowerSaving accuracy-configuration). If in the background only beacon-proximity-notifications are needed and no positioning, it is also possible to disable positioning alltogether in the background.

See the next two sections for requirements about the background-mode.

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 perform 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.

However, in our findings Apple does also allow the usage of location background mode if it is restricted to certain areas for user indoor positioning/navigation. If the Location background-mode is enabled for the app, the SDK will take advantage of "GPS" location updates to also continue ranging for beacons in the background, therefore providing the SDK-user with extended background-time. In our findings the beacon-ranging should continue indefinitely.

Please feel free to use this text for the submission form when you first submit your app / changes to the app with the sdk, or as an response to an Apple review question regarding location background mode:

Why are we using BLE?
We (Favendo) have a new technique that uses iBeacon signals to calculate a virtual indoor geoposition plus the corresponding floor level. We install iBeacons in big venues (shopping centres, train station, airports) of our clients, draw indoor maps and link the real position of the beacons to these maps. To show the users actual position on a map we use location services like GPS and WiFi for outdoor positioning, and as soon as we have the signals, the indoor position is calculated via iBeacon signals.

Is our app in compliance with App Store Review Guidelines?
Yes, we (Favendo) let this review multiple times and discussed this with the App Review Team and an Apple representative. We’re using the BLE technique to calculate the position of a customer and send her local notifications within the shopping centre and only there. As we’re using virtual geopositions plus the floor level, which isn’t possible by regular GPS and WiFi, and due to the restriction of (iBeacon) region monitoring being set to 20 regions per App in background, we’re technically forced to use background location services: "2.16 - Multitasking Apps may only use background services for […​] location, […​] local notifications, etc."

Why do you use background services and not iBeacon region monitoring
As explained above, we (Favendo) are installing the hardware in big venues with more then 300 iBeacons on a region of 200 x 200m with multiple floors. We’re not installing the iBeacons per retailer, we’re installing the iBeacons on the walls of the floors and then calculate a virtual geoposition with latitude and longitude plus a floor level. This makes it possible to notify a customer in front of a store - if she finds something interesting there - with a local notification. The restriction of the region monitoring for iBeacons to 20 regions per app and the inaccurate resoultion of the position makes it necessary to use background services to have a calculated geoposition, to notify the customer even when she has the app in background. We’re using backgound services only in the region of the shopping centre: when the customer enters the shopping centre we notify her via significant location change and ask her, if she want’s to use the feature. If she leaves the area, it will be switched off automatically. This obviously saves the battery life. This feature has been approved by Apple before explaining it via phone call with Richard Chapman from Apple, CA.

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.

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. Also, take a look at Beware of Airplane / Flight Mode

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 the 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 Backspin iOS SDK version 2.0 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 named 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.

You can also analyse ranged beacons to see if they are configured correctly:

#import <FavendoPositioning/FavendoPositioning.h>


[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rangedNewSetOfBeacons:) name:kFAVBeaconsRangedNotification object:nil];


- (void) rangedNewSetOfBeacons:(NSNotification *)beaconRangedNotification {
    NSDictionary<NSString *, NSArray<id<FAVBeaconRangeResult>>> *beaconData = beaconRangedNotification.userInfo[kFAVBeaconRangedNotificationUserInfoSmoothedBeacons];

    for(NSString *uuid in beaconData) {
        NSArray<id<FAVBeaconRangeResult>> *beaconsForThisUuid = beaconData[uuid];
        // further analysing of these beacons...
    }
}

Beware of Airplane / Flight Mode

While regular beacon-ranging works if bluetooth is enabled and location-services authorization has been granted, beacon-monitoring will not work on airplane mode.

First, monitoring should be started while the device is not in airplane mode. Unfortunately there is no straight forward way to detect if the device is in airplane mode or not (there are ways to check connectivity, which does not mean that airplane mode is active).

Second, while airplane mode is enabled, no beacon-region events are triggered, even if Bluetooth is enabled.

Third, one strange phenomenon: If the device is inside a beacon-region when airplane-mode is enabled, it will still be in this region once it is disabled again, even if that might be hours later - therefore, no new beacon-region-enter event will be fired by iOS, not waking up the app if it is in the background. While there is functionality in the SDK to cover the case that the device is already inside the region, that logic only works if the app is in foreground and can execute code. In background-mode the app/SDK can only rely on enter (or exit) events that are fired by iOS.

No valid navigation route is returned

It is possible that your data is not available yet. Since with Backspin iOS SDK version 2.0, 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.

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 for 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).