Documentation
Search…
Integration
Connect the app's miles® SDK to all of your app's features to start driving your users and build retention.
iOS Integration
🚩
You have the app's miles banner up and running.
Now you need to integrate the SDK deeper into your application so that your user can: -
🧠
understand how the program works. -
🔎
discover all the functionalities that you slaved away at developing ! -
🥕
be rewarded for consistently using your application's main features.

I. Connection

1. Onboarding

Skip to the Connect section if you intend to have everyone participate in the program by default
An onboarding example

Purpose

The first thing that the user will see when clicking on the app's miles banner is the onboarding.
This onboarding has two purposes:
    🎓
    Present and explain the program
    Ask the user their consent to take part in the program

Onboarding choices

There are up to 3 buttons on the onboarding, and you must attach a listener in order to redirect the user depending on their input.
The input of the user will affect the variable OPT_IN in the following way: (default: APM_DEVICE_OPT_IN_NOT_SET)
    👍
    OK: APM_DEVICE_OPT_IN_ACCEPT
    Later: APM_DEVICE_OPT_IN_NOT_SET
    👎
    Never: APM_DEVICE_OPT_IN_REFUSE

Example with AbstractViewController lifecycle

Swift
Objective C
1
class AbstractViewController: APMDeeplinkListener {
2
3
override func viewDidAppear(_ animated: Bool) {
4
super.viewDidAppear(animated)
5
// Start listening
6
APMDeeplinkUtils.sharedInstance().add(self)
7
}
8
9
override func viewWillDisappear(_ animated: Bool) {
10
super.viewWillDisappear(animated)
11
// Stop listening
12
APMDeeplinkUtils.sharedInstance().remove(self)
13
}
14
15
//MARK: - APMDeeplinkListener
16
17
func apmDeeplinkOnboardingButtonOkClicked() {
18
// User clicked "OK" -> "I want to participate".
19
// TODO: If user not connected, redirect to login/create-account page.
20
// Nothing to do if user connected
21
}
22
23
func apmDeeplinkOnboardingButtonLaterClicked() {
24
// User clicked "Later" -> "I'll decide later".
25
// The SDK will continue showing the onboarding when clicking the banner
26
// Nothing to do here in general
27
}
28
29
func apmDeeplinkOnboardingButtonNeverClicked() {
30
// User clicked "Never" => "Stop asking me, hide the banner"
31
// Note: The user may change his mind, and it is recommended to have a button
32
// that allows the user to get back in the program
33
// TODO: (Optional) You can redirect to the page with that "Get back in the program" button
34
// (with a little message saying "You can always change your mind by clicking this button")
35
}
36
}
Copied!
1
@interface AbstractViewController() <APMDeeplinkListener>
2
@end
3
4
@implementation AbstractViewController
5
6
- (void)viewDidAppear:(BOOL)animated {
7
[super viewDidAppear:animated];
8
// Start listening
9
[[APMDeeplinkUtils sharedInstance] addListener:self];
10
}
11
12
- (void)viewWillDisappear:(BOOL)animated {
13
[super viewWillDisappear:animated];
14
// Stop listening
15
[[APMDeeplinkUtils sharedInstance] removeListener:self];
16
}
17
18
#pragma mark - APMDeeplinkListener
19
20
-(void)apmDeeplinkOnboardingButtonOkClicked {
21
// User clicked "OK" -> "I want to participate".
22
// TODO: If user not connected, redirect to login/create-account page.
23
// Nothing to do if user connected
24
}
25
26
-(void)apmDeeplinkOnboardingButtonLaterClicked {
27
// User clicked "Later" -> "I'll decide later".
28
// The SDK will continue showing the onboarding when clicking the banner
29
// Nothing to do here in general
30
}
31
32
-(void)apmDeeplinkOnboardingButtonNeverClicked {
33
// User clicked "Never" => "Stop asking me, hide the banner"
34
// Note: The user may change his mind, and it is recommended to have a button
35
// that allows the user to get back in the program
36
// TODO: (Optional) You can redirect to the page with that "Get back in the program" button
37
// (with a little message saying "You can always change your mind by clicking this button")
38
}
39
40
@end
Copied!

Connected or non-connected

There can be two different onboardings:
    for non-connected users
    for connected users
Both of these onboardings only appear if the user has not decided whether they want to opt in or not.
An onboarding only appears if the optIn value is NOT_SET. If you want to force the optIn and still wish to present the program, it is better to use tutorials or walkthroughs (both can be configured in our BackOffice and require no code).

2. Connect

In order to offer a seamless experience, app's miles doesn't have a login or create-account page. The login/create-account process for app's miles is parallel to yours.

APMUIServicesUser.userClientConnect()

This method can both connect and create an account:
    If an app's miles account already exists with the given userID, the method signs them in.
    If no account exists, the method will automatically create their account and sign them in.
This method needs : « email », « userID » and « optIn » « email » is the email of user « userID » is a unique identifier in your database – « optIn » is the RGPD OptIn
If you don’t want give the email, you can build a encrypted email with the userID: {userID}@{your-company-name}.com (e.g. [email protected])
Swift
Objective C
1
let email: String = <EMAIL> //Required
2
let userId: String = <USER_ID> //Required
3
4
//optIn :
5
//- APM_DEVICE_OPT_IN_NOT_SET
6
//- APM_DEVICE_OPT_IN_REFUSE
7
//- APM_DEVICE_OPT_IN_ACCEPT
8
let optIn: Int = Int(APM_DEVICE_OPT_IN_ACCEPT) //Required
9
10
APMUIServicesUser.userClientConnect(email, partnerClientId: userId, optIn: optIn) { (user: APMUser?) in
11
print(user, " --> connected !")
12
} failure: { (error: Error?) in
13
print(error)
14
}
Copied!
1
NSString* email = _textFieldEmail.text; //Required
2
NSString* userId = _textFieldUserId.text; //Required
3
4
//optIn :
5
//- APM_DEVICE_OPT_IN_NOT_SET
6
//- APM_DEVICE_OPT_IN_REFUSE
7
//- APM_DEVICE_OPT_IN_ACCEPT
8
NSNumber* optIn = APM_DEVICE_OPT_IN_ACCEPT; //Required
9
10
[APMUIServicesUser userClientConnect:email
11
partnerClientId:userId
12
optIn:optIn
13
userConnectSuccess:^(APMUser *user) {
14
NSLog(@"%@ --> connected !", user);
15
} failure:^(NSError *error) {
16
NSLog(@"%@", error);
17
}];
Copied!

3. Logout

The same way the login process is parallel to yours, so is the logout

APMUIServicesUser.userLogout()

Swift
Objective C
1
APMUIServicesUser.userLogout {
2
print("Logout success")
3
} failure: { (error: Error?) in
4
print("Logout error : ", error)
5
}
Copied!
1
[APMUIServicesUser userLogout:^{
2
NSLog(@"Logout success");
3
} failure:^(NSError *error) {
4
NSLog(@"Logout error : %@", error);
5
}];
Copied!

II. Tagging plan

1. Send an action

In order to reward the user for an action, you need to place triggers in your application. You will need to follow the Tagging plan that your marketing team created with us.
Be careful with the spelling, an extra character like a 's' will change the tag
Call the method triggerAction to send a tag
Swift
Objective C
1
//"display_product" --> actionName
2
APM.sharedInstance().triggerAction("display_product")
Copied!
1
//"display_product" --> actionName
2
[[APM sharedInstance] triggerAction:@"display_product"];
Copied!
Make sure to test each action by looking at the logs, especially for actions that are complex
Earn animation

2. Actions with properties

If your tagging plan requires the use of filters, you may need to send properties with your action.
Call triggerAction with a second parameter to send a tag with properties – property.key : Property keys must be strings and must be in the tagging plan (e.g. « zipcode », « establishment »). – property.value : Property values must be strings. (e.g. « 33000 », « restaurant »).
Swift
Objective C
1
let properties: NSMutableDictionary = NSMutableDictionary()
2
properties["zipcode"] = "33000"
3
properties["establishment"] = "restaurant"
4
APM.sharedInstance().triggerAction("write_review", properties: properties)
Copied!
1
NSMutableDictionary* properties = [NSMutableDictionary dictionary];
2
properties[@"zipcode"] = @"33000";
3
properties[@"establishment"] = @"restaurant";
4
[[APM sharedInstance] triggerAction:@"write_review" properties:properties];
Copied!
This example tag will match with the action « Write a review of a restaurant in Bordeaux ».
A tag may have multiple versions configured in our BackOffice. This is an unlikely scenario, but as an example, sending the same tag and properties 3 times may match with a different version each time.

Send multiple actions

If you need to send multiple actions at once, you must use another method:
Swift
Objective C
1
var tags: [String] = []
2
tags.append("tag1")
3
tags.append("tag2")
4
tags.append("tag3")
5
APM.sharedInstance()?.triggerActions(tags)
Copied!
1
NSMutableArray* tags = [NSMutableArray array];
2
[tags addObject:@"tag1"];
3
[tags addObject:@"tag2"];
4
[tags addObject:@"tag3"];
5
[[APM sharedInstance] triggerActions:tags];
Copied!

3. User properties

Just like for 2. Send an action with properties, if your tagging plan requires the use of filters, you may need to send us userProperties so that we can offer filtered actions to the user.
Use the class APMUserPropertiesUtils to manager user properties fo user.
key : Property keys are strings. (e.g. « zipcode », « profession »). value :Property values are strings. (e.g. « 33000 », « doctor »).

Adding user properties

You can add user properties with the followings methods :
    addUserProperty()
    addUserProperties()
Swift
Objective C
1
let userPropertiesUtils: APMUserPropertiesUtils = APMUserPropertiesUtils.sharedInstance()
2
3
// Single userProperty
4
userPropertiesUtils.addUserProperty("premium", forKey: "membership_level")
5
6
// Multiple userProperties
7
var userProperties: [AnyHashable : Any] = [:]
8
userProperties["zipcode"] = "33000"
9
userProperties["membership_level"] = "premium"
10
userPropertiesUtils.addUserProperties(userProperties)
Copied!
1
APMUserPropertiesUtils* userPropertiesUtils = [APMUserPropertiesUtils sharedInstance];
2
3
// Single userProperty
4
[userPropertiesUtils addUserProperty:@"premium" forKey:@"membership_level"];
5
6
// Multiple userProperties
7
NSMutableDictionary* userProperties = [NSMutableDictionary dictionary];
8
userProperties[@"zipcode"] = @"33000";
9
userProperties[@"membership_level"] = @"premium";
10
[userPropertiesUtils addUserProperties:userProperties];
Copied!
Adding a userProperty with a key that already exists will update it.

Removing user properties

If a userProperty is no longer valid, you can remove it (example: user does not wish to be geolocated anymore)
    removeUserProperty()
    removeUserProperties()
Swift
Objective C
1
let userPropertiesUtils: APMUserPropertiesUtils = APMUserPropertiesUtils.sharedInstance()
2
3
// Single userProperty
4
userPropertiesUtils.removeUserProperty("zipcode")
5
6
// All userProperties
7
userPropertiesUtils.removeUserProperties()
Copied!
1
APMUserPropertiesUtils* userPropertiesUtils = [APMUserPropertiesUtils sharedInstance];
2
3
// Single userProperty
4
[userPropertiesUtils removeUserProperty:@"zipcode"];
5
6
// All userProperties
7
[userPropertiesUtils removeUserProperties];
Copied!

🔄
Refreshing after a change

If you have changed a userProperty that may have an impact on the actions that we can propose to the user, you will want to refresh the SDK (i.e. tell it to communicate with our API to get updated info).
You can do this by calling:
Swift
Objective C
1
APMServices.sharedInstance().refreshSDK(true)
Copied!
1
[[APMServices sharedInstance] refreshSDK:YES];
Copied!
We showcase one action above others (Challenge of the day). Each action can be configured in our BackOffice with a deeplink so that the user can go to the screen where they can perform the action.
Clicking on a suggested action's deeplink
To listen to the click on the "Let's go !"
👉
button and get the deeplink URI, you need to use APMDeepLinkActionUtils and APMDeepLinkActionUtilsListener.

Example with AbstractViewController lifecycle.

Swift
Objective C
1
class AbstractViewController: APMDeeplinkListener {
2
3
override func viewDidAppear(_ animated: Bool) {
4
super.viewDidAppear(animated)
5
// Start listening
6
APMDeeplinkUtils.sharedInstance().add(self)
7
}
8
9
override func viewWillDisappear(_ animated: Bool) {
10
super.viewWillDisappear(animated)
11
// Stop listening
12
APMDeeplinkUtils.sharedInstance().remove(self)
13
}
14
15
//MARK: - APMDeeplinkListener
16
17
func apmDeeplinkActionButtonGoClicked(_ url: String, action: String?, params: NSMutableDictionary?) {
18
// The user clicked the "Let's go" button.
19
// The big badge will automatically close
20
// TODO: Use the deeplink to navigate the user to the indicated screen.
21
}
22
}
Copied!
1
@interface AbstractViewController() <APMDeeplinkListener>
2
@end
3
4
@implementation AbstractViewController
5
6
- (void)viewDidAppear:(BOOL)animated {
7
[super viewDidAppear:animated];
8
// Start listening
9
[[APMDeeplinkUtils sharedInstance] addListener:self];
10
}
11
12
- (void)viewWillDisappear:(BOOL)animated {
13
[super viewWillDisappear:animated];
14
// Stop listening
15
[[APMDeeplinkUtils sharedInstance] removeListener:self];
16
}
17
18
#pragma mark - APMDeeplinkListener
19
20
- (void)apmDeeplinkActionButtonGoClicked:(NSString *)url action:(NSString *)action params:(NSMutableDictionary *)params {
21
// The user clicked the "Let's go" button.
22
// The big badge will automatically close
23
// TODO: Use the deeplink to navigate the user to the indicated screen.
24
}
25
26
@end
Copied!

III. Advanced functions

1. Show/Hide badge

You can hide the badge if you have a screen where you don't want the user to be distracted (e.g. checkout).
You can hide the badge with the method:
Swift
Objective C
1
APM.sharedInstance().badgeEnabled = false
Copied!
1
[[APM sharedInstance] setBadgeEnabled:NO];
Copied!
And show the badge:
Swift
Objective C
1
APM.sharedInstance().badgeEnabled = true
Copied!
1
[[APM sharedInstance] setBadgeEnabled:YES];
Copied!
Make sure to show the badge again once you leave the screen or the user may never see it again !
Day... Night...
While the badge is hidden, the SDK continues to work. If there are triggerActions, the notifications will happen once the badge is shown.

2. Activate debug logs
🐞

Logs are quire useful, right ? Here's how to unleash the (controlled) fury of debug logs:
Swift
Objective C
1
APM.sharedInstance().setDebugMode(true)
Copied!
1
[APM sharedInstance].debugMode = YES;
Copied!
iOS logs from console.app

3. Listen for changes to a user

You can be notified if the user change with the interface APMServicesUserListener. Set a listener with :
Swift
Objective C
1
APMServices.sharedInstance().servicesUserListener = self
Copied!
1
[APMServices sharedInstance].servicesUserListener = self;
Copied!
3 callbacks available :
    apmServicesUserChanged(APMUser user) : called when one of the user's attribute change
    apmServicesUserBalanceChanged(APMUser user) : called when the userBalance change
    apmServicesUserUnsubscribed() : called when the user unsubscribes

4. RGPD

You can disabled some features for RGPD compliances
Swift
Objective C
1
//Disabled localization of user for SDK app's miles
2
APMGeolocUtils.sharedInstance().setGeolocEnabled(false)
Copied!
1
//Disabled localization of user for SDK app's miles
2
[[APMGeolocUtils sharedInstance] setGeolocEnabled:NO];
Copied!

5. Add attributes/customDimensions/userProperties in your analytics

You can retrieve information from SDK to powered your analytics
    "apm_user_connected" : "yes", "no"
    "apm_user_animated" : "not_set", "animated", "anonymous", "unsubscribe"
Swift
Objective C
1
func updateUserPropertiesAPM() {
2
let user: APMUser? = APMServices.sharedInstance().user
3
let device: APMDevice? = APMServices.sharedInstance().device
4
5
let apmUserConnected: String = (user != nil && user!.isConnected()) ? "yes" : "no"
6
print("apmUserConnected : ", apmUserConnected)
7
8
var apmUserAnimated: String = "not_set"
9
if(device != nil && device!.deviceStatus != nil) {
10
let deviceStatus: NSNumber = device!.deviceStatus
11
if(deviceStatus.intValue == Int(APM_DEVICE_STATUS_OK)) {
12
apmUserAnimated = "animated"
13
}
14
else if(deviceStatus.intValue == Int(APM_DEVICE_STATUS_ANONYMOUS)) {
15
apmUserAnimated = "anonymous"
16
}
17
else if(deviceStatus.intValue == Int(APM_DEVICE_STATUS_OPTIN_OFF)) {
18
apmUserAnimated = "unsubscribe"
19
}
20
}
21
print("apmUserAnimated : ", apmUserAnimated)
22
23
//Batch example
24
// let editor = BatchUser.editor()
25
// try? editor.set(attribute: apmUserConnected, forKey:"apm_user_connected")
26
// try? editor.set(attribute: apmUserAnimated, forKey:"apm_user_animated")
27
// editor.save()
28
29
//Google Analytics example
30
// tracker.set(GAIFields.customDimensionForIndex(INDEX_APM_USER_CONNECTED), value: apmUserConnected) //INDEX_APM_USER_CONNECTED index for custom dimension : apm_user_connected
31
// tracker.set(GAIFields.customDimensionForIndex(INDEX_APM_USER_ANIMATED), value: apmUserAnimated) //INDEX_APM_USER_ANIMATED index for custom dimension : apm_user_animated
32
33
//Firebase example
34
// Analytics.setUserProperty(apmUserConnected, forName: "apm_user_connected")
35
// Analytics.setUserProperty(apmUserAnimated, forName: "apm_user_animated")
36
}
Copied!
1
-(void)updateUserPropertiesAPM {
2
APMUser* user = [APMServices sharedInstance].user;
3
APMDevice* device = [APMServices sharedInstance].device;
4
5
NSString* apmUserConnected = (user && user.isConnected) ? @"yes" : @"no";
6
NSLog(@"apmUserConnected : %@", apmUserConnected);
7
8
NSString* apmUserAnimated = @"not_set";
9
if(device && device.deviceStatus) {
10
NSNumber* deviceStatus = device.deviceStatus;
11
if(deviceStatus.integerValue == APM_DEVICE_STATUS_OK) {
12
apmUserAnimated = @"animated";
13
}
14
else if(deviceStatus.integerValue == APM_DEVICE_STATUS_ANONYMOUS) {
15
apmUserAnimated = @"anonymous";
16
}
17
else if(deviceStatus.integerValue == APM_DEVICE_STATUS_OPTIN_OFF) {
18
apmUserAnimated = @"unsubscribe";
19
}
20
}
21
NSLog(@"apmUserAnimated : %@", apmUserAnimated);
22
23
//Batch example
24
// BatchUserDataEditor *editor = [BatchUser editor];
25
// [editor setAttribute:apmUserConnected forKey:@"apm_user_connected"];
26
// [editor setAttribute:apmUserAnimated forKey:@"apm_user_animated"];
27
// [editor save];
28
29
//Google Analytics example
30
// [tracker set:[GAIFields customDimensionForIndex:INDEX_APM_USER_CONNECTED] value:apmUserConnected]; //INDEX_APM_USER_CONNECTED index for custom dimension : apm_user_connected
31
// [tracker set:[GAIFields customDimensionForIndex:INDEX_APM_USER_ANIMATED] value:apmUserAnimated]; //INDEX_APM_USER_ANIMATED index for custom dimension : apm_user_animated
32
33
//Firebase example
34
// [FIRAnalytics setUserPropertyString:apmUserConnected forName:@"apm_user_connected"];
35
// [FIRAnalytics setUserPropertyString:apmUserAnimated forName:@"apm_user_animated"];
36
}
Copied!
Call the method updateUserPropertiesAPM() :
    after the init of SDK APM (application:didFinishLaunchingWithOptions)
    when the user changed (use the listener APMServicesUserListener)

6. Enabled/Disabled statistics for device/user

You can activate/deactivate statistics analytics with the following method :
Swift
Objective C
1
let statUtils: APMStatUtils = APMStatUtils.sharedInstance()
2
statUtils.setEnabledOptinStat(true) { (optins: APMOptins?) in
3
if(optins != nil && optins!.statistics.isEnabled()) {
4
//Statistics is now activated for this device/user
5
}
6
} failure: { (error: Error?) in
7
//An error occured
8
}
Copied!
1
APMStatUtils* statUtils = [APMStatUtils sharedInstance];
2
[statUtils setEnabledOptinStat:YES userOptinsSuccess:^(APMOptins *optins) {
3
if([optins.statistics isEnabled]) {
4
//Statistics is now activated for this device/user
5
}
6
} failure:^(NSError *error) {
7
//An error occured
8
}];
Copied!
If we are connected to an user account, this setting is set to user and device. If we are not connected, this setting is set to device only.
By default, statistics analytics are disabled.
Last modified 19d ago