#Table of Contents
- 1. System Requirements
- 2. SDK Initialization
- 3. Feature Activation
- 4. Submitting your App
#1. System Requirements The R1ConnectEngage SDK supports all mobile and tablet devices running iOS 7.0 or newer (6.0 support coming soon!) with a base requirement of Xcode 6.0 used for development (as you must build using iOS 8 SDK or newer). The downloadable directory (see below "a. Import Files") contains the library and headers for the R1ConnectEngage SDK.
The library supports the following architectures:
- arm7
- arm7s
- arm64
Support for iOS Simulator is also included for the following architectures
- i386
- x86_64
#2. SDK Initialization
- Download the R1ConnectEngage SDK files: git clone [email protected]:radiumone/r1-engage-iOS.git
- Open your iOS project in Xcode.
- Select File -> Add Files to “[YOUR XCODE PROJECT]” project
- Select all files in the "Lib" Folder from the repo you just cloned
- When the dialog box appears, check the "Copy Items into destination group’s folder" checkbox.
See image below:
![Image of addfiles] (Doc_Images/addfiles.png)
Go to "Build Phases" and make sure libR1ConnectEngage.a file is set in the “Link Binary With Libraries” section. If absent, please add it.
Make sure you add:
- CoreLocation.framework
- CoreBluetooth.framework
- AdSupport.framework
- CoreTelephony.framework
- SystemConfiguration.framework
- libsqlite3.dylib
- StoreKit.framework
- WebKit.framework - should be marked as Optional instead of Required
See image below:
![Image of framework] (Doc_Images/framework.png)
It is important to add an entry to the 'Other Linker Flags' setting in your application's Build Settings in Xcode. Add '-ObjC' to the 'Other Linker Flags' setting if it is not already present. This is a common required flag when integrating static libraries with your code.
You will need to initialize the R1ConnectEngage Library in your App Delegate. ####Import the required header files At the top of your application delegate include any required headers:
#import "R1SDK.h"
####Initialize the R1ConnectEngage SDK Instance
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
R1SDK *sdk = [R1SDK sharedInstance];
// Initialize SDK
sdk.applicationId = @"YOUR APPLICATION ID"; //Ask your RadiumOne contact for an app id
return YES;
}
#3. Feature Activation
##a. Engage Activation
R1ConnectEngage SDK supports iOS 6.0 and above. It supports full-screen products (Offerwall, Interstitial, Video) as well as Banners that you can place how you like in your application. Engage also supports ad mediation allowing you to optionally pull from a larger pool of advertisements, helping you to maximize the monetization of your application.
Engage needs to be enabled in the application's App Delegate. In the application: didFinishLaunchingWithOptions:
method insert the following code:
R1SDK *sdk = [R1SDK sharedInstance];
sdk.engageEnabled = YES;
Engage has support for three full-screen ad views: Offerwall, Interstitial, Video. In addition to these full-screen ad views, banner ads (of various sizes) are available as well.
All ad views are managed via proxy objects. AdViewProxy objects provide your interface the ability to configure, load, show and respond to status updates of an ad. You can create each type of ad proxy via the 'adServerManager' object available via the 'R1EngageSDK' shared object.
The methods used to create the different AdViewProxy objects are:
- bannerAdViewProxy
- interstitialAdViewProxy
- videoAdViewProxy
- offerwallAdViewProxy
AdViewProxy objects created with these methods will be one of the corresponding types:
- R1BannerAdProxy
- R1InterstitialAdProxy
- R1VideoAdProxy
- R1OfferwallAdProxy
As an example, you can create an interstitial proxy with the following line of code:
self.adProxy = [[R1EngageSDK sharedInstance].adServerManager interstitialAdViewProxy];
In the above example, self.adProxy refers to a property of type R1InterstitialAdProxy of the calling class instance. It is required that you retain (or, when using ARC, have a strong reference to) the created adProxy instance for the lifetime of the adView as it will release all of its managed views and controllers once no other object is holding on to it.
To continue our example, our property, 'adProxy' would be declared in the owning class as:
@property (nonatomic, strong) R1InterstitialAdProxy *adProxy; // when using ARC
or
@property (nonatomic, retain) R1InterstitialAdProxy *adProxy; // when using classic memory management
After the adViewProxy has been created and retained as detailed above, it must be configured prior to loading an ad. This is a fairly straightforward process as there are just three attributes to set:
* where the ad will be displayed
* attaching any placement identifiers needed to display the ad or track user engagement
* setting up callbacks for status updates from the adViewProxy.
All of the adViewProxy objects require a root view controller to be specified for their display (for the three full-screen ad views) or for the display of a supplemental full-screen view that may be presented when a banner ad receives a user tap. The view controller passed in should be the root view controller for whatever view is meant to show the advertisement. i.e. if the active view is part of a navigation view controller or tab view controller, then those parent view controllers should be passed in.
// assuming self is a view controller in a tab view
[self.adProxy setRootViewControllerForAdPresentation:self.parentViewController];
PlacementIds identify, sometimes uniquely, one ad placement from another. Because Engage can support multiple ad networks via its mediation feature, placementIds allow the setting of an identification tag for each ad and network that is enabled in the application. Using this setting with mediation will be discussed in more depth later. It is advisable that a unique value be set for Engage for your own tracking needs. It can be any custom value of your choosing (we will save and return back up to 100 characters of it). Since it is possible to set a placement id for more than one ad network at a time, the values are set as part of a dictionary.
Continuing from the previous code example the placementIds can be set:
NSMutableDictionary *dataDict = [NSMutableDictionary dictionaryWithCapacity:1];
[dataDict setObject:@"level1Interstitial" forKey:R1AdNetworkEngage];
self.adProxy.placementIds = dataDict;
As the AdViewProxy completes various actions or encounters specific states, it will send notifications to registered listeners. The messages it will send are:
- R1AdStateLoadedNotification; // Your application initiated the loading of an ad in a view and it has successfully completed and is ready to show
- R1AdStateFailedNotification; // Your application initiated the loading of an ad in a view and it failed to load due to an error
- R1AdStateNoContentNotification; // Your application initiated the loading of an ad in a view and it failed to load due to no advertisement being available to serve
- R1AdStateWillAppearNotification; // Your application initiated the display of an ad or a user tapped on a banner ad
- R1AdStateWillDisappearNotification; // A full-screen advertising view is about to be dismissed but is still visible
- R1AdStateDidDisappearNotification; // A full-screen advertising view finished being dismissed and is no longer visible - time to dispose of ad proxy
While the application may want to respond to any of these notifications, one that is particularly important is the 'R1AdStateLoadedNotification' as this can act as the trigger to show the ad or otherwise prepare to show the ad.
An example of how to register for this notification may look something like:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(interstitialLoaded:) name:R1AdStateLoadedNotification object:self.adProxy];
In this case, 'self' is the object that will receive the 'interstitialLoaded:' callback. Because self.adProxy was specified as the object, only R1AdStateLoadedNotifications from that AdViewProxy will be sent to our 'self' object.
An implementation of the notification handler might look like this:
- (void)interstitialLoaded:(NSNotification *)notification
{
// Now that the ad is loaded, show it immediately
[self.adProxy showInterstitial];
}
In the above example, as soon as the R1AdStateLoadedNotification is received, the application makes the call to show the interstitial ad view. But your application might also use this notification as a point to initiate some other action before finally showing the advertisement.
Note that the three full-screen ad types: interstitial, video and offerwall, can all be handled correctly with the preceding code sample. For a banner ad, the application would respond to the R1AdStateLoadedNotification slightly differently. There is no explicit 'show' method for a banner ad. Once the AdViewProxy notifies the application that a banner has loaded, it is the application's responsibility to add it to the appropriate view hierarchy as it sees fit using animation or any other available option.
- (void)bannerLoaded:(NSNotification *)notification
{
NSDictionary *userInfo = notification.userInfo;
UIView *bannerView = [userInfo objectForKey:R1BannerViewKey];
if(![bannerView superview])
{
//assuming self is a view controller
[self.view addSubview:bannerView];
}
}
The R1BannerAdProxy will include the newly loaded banner view in the notification callback in the userInfo dictionary. Use the 'R1BannerViewKey' key to access it so you can make any last minutes adjustments to the view before you ad it to the appropriate view hierarchy. Note, when your application clears its reference to a R1BannerAdProxy instance, any banner view in a view hierarchy will automatically (and immediately) remove itself from its parent view hierarchy before the AdViewProxy disposes of view.
In the previous sections, we've outlined how to create, configure and show a full-screen ad. But one critical step remains - loading the ad. Loading an advertisement is done through a distinct call on each AdProxy instance. Full-screen adViewProxy objects have one of these selectors. Each kick off the network call to load the advertisement assets.
- loadInterstitial
- loadVideo
- loadOfferwall
R1BannerAdProxy objects are only slightly different.
- loadBannerType
This selector takes an enumerated value from the following list of values to request a banner of a certain size. Note that the size of the banner returned is not guaranteed to match the requested dimensions - you must check the bounds of the view when your application receives the R1AdStateLoadedNotification.
R1_300x50BannerType
R1_320x50BannerType
R1_480x50BannerType
R1_300x250BannerType
R1_728x90BannerType
R1_1024x90BannerType
After a full-screen ad has displayed or if your application has navigated away from a view that was displaying a banner ad, it is time to release your reference to the corresponding adViewProxy instance.
// unregister your notification callback first!
[[NSNotificationCenter defaultCenter] removeObserver:self name:R1AdStateLoadedNotification object:self.adProxy];
// now it is safe to release your ad proxy reference.
self.adProxy = nil;
As a very basic example, the following demonstrates the integration of a banner ad along with an interstitial ad. Other AdViewProxys are integrated in a similar way. (Note: This demonstrates all the necessary code required to display an advertisement, but is not suggesting, for example, that you should show an interstitial ad as soon as your main view loads.)
In your main view controller header...
#import <UIKit/UIKit.h>
#import "R1EngageSDK.h"
@interface ViewController : UIViewController
@property (nonatomic, strong) R1BannerAdProxy *bannerProxy;
@property (nonatomic, strong) R1InterstitialAdProxy *interstitialProxy;
@end
In your main view controller implementation file...
- (void)viewDidLoad
{
[super viewDidLoad];
// Banner setup
self.bannerProxy = [[R1EngageSDK sharedInstance].adServerManager bannerAdViewProxy];
self.bannerProxy.placementIds = [NSDictionary dictionaryWithObjectsAndKeys:@"mainScreenBanner", R1AdNetworkEngage, nil];
[self.bannerProxy setRootViewControllerForAdPresentation:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(bannerLoaded:) name:R1AdStateLoadedNotification object:self.bannerProxy];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(bannerLoadFailed:) name:R1AdStateFailedNotification object:self.bannerProxy];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(bannerLoadFailed:) name:R1AdStateNoContentNotification object:self.bannerProxy];
[self.bannerProxy loadBannerType:R1_320x50BannerType];
// Interstitial setup
self.interstitialProxy = [[R1EngageSDK sharedInstance].adServerManager interstitialAdViewProxy];
self.interstitialProxy.placementIds = [NSDictionary dictionaryWithObjectsAndKeys:@"mainScreenInterstitial", R1AdNetworkEngage, nil];
[self.interstitialProxy setRootViewControllerForAdPresentation:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(interstitialLoaded:) name:R1AdStateLoadedNotification object:self.interstitialProxy];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(interstitialDismissed:) name:R1AdStateDidDisappearNotification object:self.interstitialProxy];
[self.interstitialProxy loadInterstitial];
}
- (void)bannerLoaded:(NSNotification *)notification
{
NSDictionary *userInfo = notification.userInfo;
UIView *bannerView = [userInfo objectForKey:R1BannerViewKey];
if(![bannerView superview])
{
[self.view addSubview:bannerView];
}
}
- (void)bannerLoadFailed:(NSNotification *)notification
{
NSString *noteName = notification.name;
if([noteName isEqualToString:R1AdStateFailedNotification])
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:self.bannerProxy];
// release our proxy
// since it is a general error we can try again shortly
self.bannerProxy = nil;
}
else if([noteName isEqualToString:R1AdStateNoContentNotification])
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:self.bannerProxy];
// release our proxy
// since there is no content, we can try a different size or just wait
// awhile to see if more ad inventory is available later
self.bannerProxy = nil;
}
}
- (void)interstitialLoaded:(NSNotification *)notification
{
// The interstitial is ready to be displayed, so go ahead and show it
[self.interstitialProxy showInterstitial];
}
- (void)interstitialDismissed:(NSNotification *)notification
{
// The user dismissed the ad - clean up and release our reference to the proxy
[[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:self.interstitialProxy];
self.interstitialProxy = nil;
}
Ad mediation allow your application to pull from a wider pool of ad networks to fulfill an ad request giving you confidence that there will be ads to display when you want them. Engage supports mediation by enabling ad fulfillment via the following ad networks:
- Google AdMob - https://www.google.com/ads/admob/
- MoPub - http://www.mopub.com
To enable mediation in your application is a four step process.
-
Set up an account and create placement ids (adUnits) in the AdMob, MoPub web portals. AdUnitIds are tied to each ad you place in your application. You should create a unique AdUnitId for each ad placement in your application.
-
Add the mediation adapter libraries (of each desired network) to your project. These mediation adapter libraries can be found in the 'Mediation Adapters" subfolder in the SDK folder that you downloaded.
-
libR1AdMobMediationAdapter.a
-
libR1MoPubMediationAdapter.a
Go to the "Build Phases" tab of your target application's Xcode build settings and make sure the desired library file (from the above list) is set in the “Link Binary With Libraries” section. If absent, please add it.
- In your code, enable mediation behavior by setting the 'mediationEnabled' flag. This can be done anywhere but a recommended spot would be the place in your code where you enable the Engage module.
[R1EngageSDK sharedInstance].adServerManager.mediationEnabled = YES;
- The adUnit ids that you generate in the web portals must be used to initialize each adViewProxy instance created using the 'placementIds' property of the adViewProxy.
NSMutableDictionary *dataDict = [NSMutableDictionary dictionaryWithCapacity:3];
[dataDict setObject:@"mainScreenBanner" forKey:R1AdNetworkEngage];
[dataDict setObject:@"YourAdMobAdUnitId" forKey:R1AdNetworkAdMob];
[dataDict setObject:@"YourMoPubAdUnitId" forKey:R1AdNetworkMoPub];
self.bannerProxy.placementIds = dataDict;
Okay, to support mediation via MoPub there actually is a fifth step (sorry!) 5) Add the MoPub specific resource files to your Xcode project. Alongside the libR1MoPubMediationAdapter.a file, you should find a 'MoPubResources' folder which contains:
MPCloseButtonX.png [email protected] [email protected] MRAID.bundle MPAdBrowserController.xib
These files are all required to be included in your application for MoPub advertisements to operate correctly. Again, these files are only required if you have mediation enabled and MoPub setup. You can add the png image files to your Xcode project's Images.xcassets catalog. The MRAID.bundle and MPAdBrowserController.xib can be placed with any of the other auxiliary resource files in your project. e.g. application plist.
That's all that is required in your app! It is required to also configure the rules by which Engage will execute mediation. The mediation rules can be managed via the Engage web portal (http://gwallet.net/gwallet-admin). Now, whenever, you request an advertisement, Engage will automatically attempt to fill the request according to the rules you specified in the Engage web portal.
####Checking rewards information
To receive rewards information setup the delegate:
[R1EngageSDK sharedInstance].delegate = self;
And add method to the code:
- (void)engageSDK:(R1EngageSDK *)r1engage didReceiveNewReward:(NSUInteger)reward
{
//your code here
}
On close of any full-screen view controller, the Engage engine will automatically check for new rewards. But you can do it manually (for example after application launching):
[[R1EngageSDK sharedInstance] checkCompletions];
##b. Analytics Activation
#import "R1SDK.h"
#import "R1Emitter.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
R1SDK *sdk = [R1SDK sharedInstance];
// Initialize SDK
sdk.applicationId = @"YOUR APPLICATION ID"; //Ask your RadiumOne contact for an app id
// Start Analytics
[sdk start];
return YES;
}
The following is a list of configuration parameters for the Analytics. Most of these contain values that are sent to the tracking server to help identify your app on our platform and to provide analytics on sessions and location.
####Configuration Parameters
applicationUserId
Optional current user identifier.
[R1SDK sharedInstance].applicationUserId = @"12345";
location
The current user location coordinates. Use only if your application already uses location services.
[R1SDK sharedInstance].location = [locations lastObject];
locationService
If your application did not use location information before this SDK installation, you can use locationService in this SDK to enable or disable it:
[R1SDK sharedInstance].locationService.enabled = YES;
When enabled, such as in the example above, location information will be sent automatically. However, locationService doesn’t fetch the location constantly. For instance, when the location is received the SDK will turn off the location in CLLocationManager and wait 10 minutes (by default) before attempting to retrieve it again. You can change this value:
[R1SDK sharedInstance].locationService.autoupdateTimeout = 1200; // 20 minutes
N.B. - The Connect locationService uses the Location Manager in iOS. For deployment on iOS 8 and newer, it is required that the application's property list (plist) file includes one of the two following keys:
NSLocationAlwaysUsageDescription //provide location updates whether the user is actively using the application or not via infrequent background location updates // OR NSLocationWhenInUseUsageDescription //provide location updates only while the user has your application as the foreground running app
These are string values that need to include a description suitable to present to a user. The description should explain the reason the application is requesting access to the user's location. Therefore, description you give should try to incorporate or reference the user benefit that may be possible through sharing location.
appName
The application name associated with the emitter. By default, this property is populated with the CFBundleName
string from the application bundle. If you wish to override this property, you must do so before making any tracking calls.
[R1Emitter sharedInstance].appName = @"Custom application name";
appId
Default: nil
The application identifier associated with this emitter. If you wish to set this property, you must do so before making any tracking calls. Note: that this is not your app's bundle id, like e.g. com.example.appname.
[R1Emitter sharedInstance].appId = @"12345678";
appVersion
The application version associated with this emitter. By default, this property is populated with the CFBundleShortVersionString
string from the application bundle. If you wish to override this property, you must do so before making any tracking calls.
[R1Emitter sharedInstance].appVersion = @"1.0.2";
sessionStart
If true, indicates the start of a new session. Note that when a emitter is first initiated, this is initialized to true. To prevent this default behavior, set this to NO
when the tracker is first obtained.
Setting this does not send any data. If this is true, when the next emitter call is made, a parameter will be added, resulting in the emitter information indicating the start of a session. This flag will be cleared.
[R1Emitter sharedInstance].sessionStart = YES;
// Your code here
[R1Emitter sharedInstance].sessionStart = NO;
sessionTimeout
If positive, indicates how long (in seconds) the application must transition to the inactive or background state for before the tracker will automatically indicate the start of a new session. This occurs when the app becomes active again by setting sessionStart to true. For example, if this is set to 30 seconds, and the user receives a phone call that lasts for 45 seconds while using the app, upon returning to the app, the sessionStart parameter will be set to true. If the phone call lasted 10 seconds, sessionStart will not be modified.
To disable automatic session tracking, set this to a negative value. To indicate the start of a session anytime the app becomes inactive or backgrounded, set this to zero.
By default, this is 30 seconds.
[R1Emitter sharedInstance].sessionTimeout = 15;
The R1ConnectEngage SDK will automatically capture some generic events in order to get the most meaningful data on how users interact with your app. These events are triggered when the state of the application is changed, and therefore do not require any additional code to work out of the box:
Launch - emitted when the app starts
First Launch - emitted when the app starts for the first time
First Launch After Update - emitted when the app starts for the first time after a version upgrade
Suspend - emitted when the app is put into the background state
Resume - emitted when the app returns from the background state
Application Opened - emitted when the app is opened and can measure when your app is opened after a message is sent
Session Start - emitted when a new session begins
Session End - emitted when a session ends; includes a Session Length attribute with the session length in seconds.
Standard events cover all the main user flows (login, register, share, purchase...) in a standardized format for optimized reporting on the portal, providing a great foundation to your analytics strategy. Once you set them up in your code, they unlock great insights, particularly on user lifetime value.
Note: The last argument in all of the following emitter callbacks, otherInfo, is a dictionary of “key”,”value” pairs or nil, which enables you to customize these events as much as you want.
Login
Tracks a user login within the app
[[R1Emitter sharedInstance] emitLoginWithUserID:@"userId"
userName:@"user_name"
otherInfo:@{@"custom_key":@"value"}];
Registration
Records a user registration within the app
[[R1Emitter sharedInstance] emitRegistrationWithUserID:@"userId"
userName:@"userName"
country:@"country"
state:@"state"
city:@"city"
otherInfo:@{@"custom_key":@"value"}];
Facebook connect
Allows access to Facebook services
NSArray *permissions = @[[R1EmitterSocialPermission socialPermissionWithName:@"photos" granted:YES]];
[[R1Emitter sharedInstance] emitFBConnectWithPermissions:permissions
otherInfo:@{@"custom_key":@"value"}];
Twitter connect
Allows access to Twitter services
NSArray *permissions = @[[R1EmitterSocialPermission socialPermissionWithName:@"photos" granted:YES]];
[[R1Emitter sharedInstance] emitTConnectWithUserID:@"12345"
userName:@"user_name"
permissions:permissions
otherInfo:@{@"custom_key":@"value"}];
User Info
Enables you to send user profiles to the backend.
R1EmitterUserInfo *userInfo = [R1EmitterUserInfo userInfoWithUserID:@"userId"
userName:@"userName"
email:@"[email protected]"
firstName:@"first name"
lastName:@"last name"
streetAddress:@"streetAddress"
phone:@"phone"
city:@"city"
state:@"state"
zip:@"zip"];
[[R1Emitter sharedInstance] emitUserInfo:userInfo
otherInfo:@{@"custom_key":@"value"}];
Upgrade
Tracks an application version upgrade
[[R1Emitter sharedInstance] emitUpgradeWithOtherInfo:@{@"custom_key":@"value"}];
Trial Upgrade
Tracks an application upgrade from a trial version to a full version
[[R1Emitter sharedInstance] emitTrialUpgradeWithOtherInfo:@{@"custom_key":@"value"}];
Screen View
A page view that provides info about that screen
[[R1Emitter sharedInstance] emitScreenViewWithDocumentTitle:@"title"
contentDescription:@"description"
documentLocationUrl:@"http://www.example.com/path"
documentHostName:@"example.com"
documentPath:@"path"
otherInfo:@{@"custom_key":@"value"}];
Transaction
[[R1Emitter sharedInstance] emitTransactionWithID:@"transaction_id"
storeID:@"store_id"
storeName:@"store_name"
cartID:@"cart_id"
orderID:@"order_id"
totalSale:1.5
currency:@"USD"
shippingCosts:10.5
transactionTax:12.0
otherInfo:@{@"custom_key":@"value"}];
TransactionItem
R1EmitterLineItem *lineItem = [R1EmitterLineItem itemWithID:@"product_id"
name:@"product_name"
quantity:5
unitOfMeasure:@"unit"
msrPrice:10
pricePaid:10
currency:@"USD"
itemCategory:@"category"];
[[R1Emitter sharedInstance] emitTransactionItemWithTransactionID:@"transaction_id"
lineItem:lineItem
otherInfo:@{@"custom_key":@"value"}];
Create Cart
[[R1Emitter sharedInstance] emitCartCreateWithCartID:@"cart_id"
otherInfo:@{@"custom_key":@"value"}];
Delete Cart
[[R1Emitter sharedInstance] emitCartDeleteWithCartID:@"cart_id"
otherInfo:@{@"custom_key":@"value"}];
Add To Cart
R1EmitterLineItem *lineItem = [R1EmitterLineItem itemWithID:@"product_id"
name:@"product_name"
quantity:5
unitOfMeasure:@"unit"
msrPrice:10
pricePaid:10
currency:@"USD"
itemCategory:@"category"];
[[R1Emitter sharedInstance] emitAddToCartWithCartID:@"cart_id"
lineItem:lineItem
otherInfo:@{@"custom_key":@"value"}];
Delete From Cart
R1EmitterLineItem *lineItem = [R1EmitterLineItem itemWithID:@"product_id"
name:@"product_name"
quantity:5
unitOfMeasure:@"unit"
msrPrice:10
pricePaid:10
currency:@"USD"
itemCategory:@"category"];
[[R1Emitter sharedInstance] emitDeleteFromCartWithCartID:@"cart_id"
lineItem:lineItem
otherInfo:@{@"custom_key":@"value"}];
###iv. Custom Events
Custom events allow you to create and track specific events that are more closely aligned with your app. If planned and structured correctly, custom events can be strong indicators of user intent and behavior. Some examples include pressing the “like” button, playing a song, changing the screen mode from portrait to landscape, and zooming in or out of an image. These are all actions by the user that could be tracked with events.
To include tracking of custom events for the mobile app, the following callbacks need to be included in the application code:
// Emits a custom event without parameters
[[R1Emitter sharedInstance] emitEvent:@"Your custom event name"];
// Emits a custom event with parameters
[[R1Emitter sharedInstance] emitEvent:@"Your custom event name"
withParameters:@{@"key":@"value"}];
###v. Best Practices ####Event Naming Convention One common mistake is to parameterize event names (with user data for example). Event names should be hard-coded values that you use to segment data on a specific category of event.
Example: "ProfileViewing"
Avoid: "Profile Viewing - Lady Gaga's profile"
As you may have thousands of user profiles in your database, it is preferable to keep the event name high level ("ProfileViewing") so you can run interesting analytics on it. High-level events help answer questions like "how many profiles does a user visit every day on average?"
####Parameter Variance
Another common mistake is to add parameters to the event that have too many possible values. To follow up on the previous example, one may decide to add the number of profile followers as an event parameter:
[[R1Emitter sharedInstance] emitEvent:@"ProfileViewing"
withParameters:@{"profileFollowers":profileFollowers}];
Again, the problem here is that each profile may have any number of followers. This will fragment your data too much to extract any valuable information.
A good strategy would be to define relevant buckets for high variance parameters. In this case, it might be more relevant to separate traffic on the profiles with a high follower count from traffic on profiles with a low count. You could define 3 categories:
- "VERY_INFLUENTIAL" for profiles > 100,000
- "INFLUENTIAL" for profile > 10,000 and <= 100,000
- "NON_INFLUENTIAL" for profile <= 10,000
A proper event could be
[[R1Emitter sharedInstance] emitEvent:@"ProfileViewing"
withParameters:@{"profileFollowersBucket":@"VERY_INFLUENTIAL"}];
This will enable you to create more insightful reports.
##c. Push Notification Activation
###i. Setup Apple Push Notification Services
####Prerequisites for Apple Push Notification Services Setup ######The Importance of Setting your App as in “Production” or in “Development” When creating or editing an app on RadiumOne Connect you can set the status of the app to either “Production” or “Development”. “Production” status for an app is considered to be a live app that is in the hands of real users and will have notifications and other data running on live servers. A “Development” status for an app is one that you are still performing testing on and will not be viewed by any real-life audiences because it will stay on test servers.
In the context of push notifications, it is important to know this difference because Apple will treat these two servers separately. Also device tokens for “Development” will not work on “Production” and vice versa. We recommend a Development app version and Production app version for your app on RadiumOne Connect to keep Push SSL certificates separate for each. You can also continue testing and experimenting on one app without worrying about it affecting your live app audience in any way. ######iOS Developer Program Enrollment This doc assumes that you are enrolled in the iOS Developer Program. If you are not, please enroll here. Being in the Apple Developer Program is a required component to have your iOS app communicate with the RadiumOne Connect service for push notifications and is necessary for the next step of setting up your app with the Apple Push Notification Service (APNs). It is also essential that you have “Team Agent” role access in the iOS Developer Program to complete this process.
####Configuring your App for Apple Push Notifications ######Apple Developer Members Center Make sure you are logged into the Apple Developer Members Center. Once you are logged in you can locate your application in the “Identifiers” folder list.
######Registered App ID If you have not registered an App ID yet it is important that you do so now. You will need to click the “+” symbol, fill out the form, and check the “Push Notifications” checkbox. Please keep in mind it is possible to edit these choices after the App ID is registered.
You can expand the application and when doing so you will see two settings for push notifications. They will have either yellow or green status icons like here:
You will need to click Edit to continue. If the Edit button is not visible it is because you do not have “Team Agent” role access. This role is necessary for getting an SSL certificate.
######Creating an SSL Certificate To enable the Development or Production Push SSL Certificate please click Edit. (It is important to note that each certificate is limited to a single app, identified by its bundle ID and limited to one of two environments, either Development or Production. Read more info here.)
You will see a Create Certificate button, after clicking it you will see the “Add iOS Certificate Assistant”. Please follow the instructions presented in the assistant which includes launching the “Keychain Access” application, generating a “Certificate Signing Request (CSR)”, generating an SSL Certificate, etc.
If you follow the assistant correctly, after downloading and opening the SSL Certificate you should have it added under “My Certificates” or “Certificates” in your “Keychain Access” application. Also when you are returned to the Configure App ID page the certificate should be badged with a green circle and the label “Enabled”.
######Exporting the SSL Certificate If not already in the “Keychain Access” app that contains your certificate, please open it and select the certificate that you just added. Once you select the certificate go to File > Export Items and export it as a Personal Information Exchange (.p12) file. When saving the file be sure to use the Personal Information Exchange (.p12) format.
######Emailing your SSL certificate After downloading your 2 certificates (one for production, one for development), please send them to your RadiumOne account manager (with certificate passwords if you choose to add any).
###ii. Initialization
####Setup your App Delegate
#import "R1SDK.h"
#import "R1Emitter.h"
#import "R1Push.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
R1SDK *sdk = [R1SDK sharedInstance];
// Initialize SDK
sdk.applicationId = @"Application ID"; //Ask your RadiumOne contact for an app id
// Initialize Push Notification
sdk.clientKey = @"Your Client Key"; //Ask your RadiumOne contact for a client key
[[R1Push sharedInstance] handleNotification:[launchOptions valueForKey:UIApplicationLaunchOptionsRemoteNotificationKey]
applicationState: application.applicationState];
[[R1Push sharedInstance] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeSound |
UIRemoteNotificationTypeAlert)];
// Start SDK
[sdk start];
return YES;
}
####Register for Remote Notifications
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
[[R1Push sharedInstance] registerDeviceToken:deviceToken];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
[[R1Push sharedInstance] failToRegisterDeviceTokenWithError:error];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
[[R1Push sharedInstance] handleNotification:userInfo applicationState:application.applicationState];
}
// For iOS 8 and newer, it is required to implement the following callback method
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
if(![application isRegisteredForRemoteNotifications])
{
[application registerForRemoteNotifications];
}
}
Push is disabled by default. You can enable it in the application:didFinishLaunchingWithOptions method or later.
[[R1Push sharedInstance] setPushEnabled:YES];
NOTE: If you enabled it in the application:didFinishLaunchingWithOptions method, the Push Notification AlertView will be showed at first application start.
###iii. Rich Push Initialization
Rich Push Notifications send a URL that opens in a WebView when a user responds to a system notification. No set up is required for this feature.
###iv. Deep Link Initialization
Deep linking Push Notifications open up a designated view in an application when a user responds to a system notification. To properly handle deep link push receipts, please read Apple's documentation: https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Inter-AppCommunication/Inter-AppCommunication.html#//apple_ref/doc/uid/TP40007072-CH6-SW10 http://wiki.akosma.com/IPhone_URL_Schemes
###v. Segment your Audience
You can specify Tags for R1ConnectEngage SDK to send Push Notifications to certain groups of users. You can then send Push Notifications to users with your specific tags.
The maximum length of a Tag is 128 characters.
R1ConnectEngage SDK saves Tags. You do not have to add Tags every time the application is launched.
Add a new Tag
[[R1Push sharedInstance].tags addTag:@"NEW TAG"];
Add multiple Tags
[[R1Push sharedInstance].tags addTags:@[ @"NEW TAG 1", @"NEW TAG 2" ]];
Remove existing Tag
[[R1Push sharedInstance].tags removeTag:@"EXIST TAG"];
Remove multiple Tags
[[R1Push sharedInstance].tags removeTags:@[ @"EXIST TAG 1", @"EXIST TAG 2" ]];
Replace all existing Tags
[R1Push sharedInstance].tags.tags = @[ @"NEW TAG 1", @"NEW TAG 2" ];
or
[[R1Push sharedInstance].tags setTags:@[ @"NEW TAG 1", @"NEW TAG 2" ]];
Get all Tags
NSArray *currentTags = [R1Push sharedInstance].tags.tags;
###vi. Inbox Integration If you want to enable inbox functionality, you need to use R1Inbox class and import R1Inbox.h header at the top of your class file:
#import "R1Inbox.h"
If you want to add some label or button with count of unread or total Inbox messages, you should implement -(void) inboxMessageUnreadCountChanged
or -(void) inboxMessagesDidChanged
methods from R1InboxMessagesDelegate
protocol in your class. After that, add your class as delegate to [R1Inbox sharedInstance].messages
- (void) addInboxMessagesDelegate
{
[[R1Inbox sharedInstance].messages addDelegate:self];
}
-(void) inboxMessageUnreadCountChanged
{
NSString *btnTitle = [NSString stringWithFormat:@"Inbox (%lu unread)", (unsigned long)[R1Inbox sharedInstance].messages.unreadMessagesCount];
[self.inboxButton setTitle:btnTitle forState:UIControlStateNormal];
}
Do not forget to remove your class from delegates when your class gets deallocated:
- (void) dealloc
{
[[R1Inbox sharedInstance].messages removeDelegate:self];
...
}
To display the list of Inbox messages, you should create your own ViewController to provide required customization. In this case, this screen would not look foreign to your application.
You can see the sample of Inbox implementation in DemoApplication project in files R1SampleInboxTableViewCell.h, R1SampleInboxTableViewCell.m, R1SampleInboxViewController.h, R1SampleInboxViewController.m.
R1SampleInboxTableViewCell.h
#import <UIKit/UIKit.h>
@class R1InboxMessage;
@interface R1SampleInboxTableViewCell : UITableViewCell
// Sets or Gets R1InboxMessage object and configures cell for it
@property (nonatomic, strong) R1InboxMessage *inboxMessage;
- (id) initWithReuseIdentifier:(NSString *)reuseIdentifier;
// Calculates the height of the cell
+ (CGFloat) heightForCellWithInboxMessage:(R1InboxMessage *) inboxMessage cellWidth:(CGFloat) cellWidth;
@end
R1SampleInboxTableViewCell.m
#import "R1SampleInboxTableViewCell.h"
#import "R1Inbox.h"
@interface R1SampleInboxTableViewCell ()
@property (nonatomic, strong) UIView *unreadMarker;
@property (nonatomic, strong) UILabel *alertLabel;
@property (nonatomic, strong) NSDateFormatter *dateFormatter;
@end
@implementation R1SampleInboxTableViewCell
- (id) initWithReuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier];
if (self)
{
self.alertLabel = [[UILabel alloc] initWithFrame:CGRectZero];
self.alertLabel.numberOfLines = 0;
self.alertLabel.lineBreakMode = NSLineBreakByWordWrapping;
[self.contentView addSubview:self.alertLabel];
self.unreadMarker = [[UIView alloc] initWithFrame:CGRectZero];
self.unreadMarker.layer.cornerRadius = 3;
self.unreadMarker.backgroundColor = [UIColor blueColor];
[self.contentView addSubview:self.unreadMarker];
self.textLabel.font = [UIFont boldSystemFontOfSize:14];
self.alertLabel.font = [UIFont systemFontOfSize:14];
self.detailTextLabel.textAlignment = NSTextAlignmentRight;
self.dateFormatter = [[NSDateFormatter alloc] init];
[self.dateFormatter setDateStyle:NSDateFormatterShortStyle];
[self.dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
}
return self;
}
// Configures the cell for displaying Inbox Message
- (void) setInboxMessage:(R1InboxMessage *)inboxMessage
{
if (_inboxMessage == inboxMessage)
{
[self configureUnreadMarker];
return;
}
_inboxMessage = inboxMessage;
self.textLabel.text = inboxMessage.title;
self.alertLabel.text = inboxMessage.alert;
self.detailTextLabel.text = [self.dateFormatter stringFromDate:inboxMessage.createdDate];
[self configureUnreadMarker];
[self setNeedsLayout];
}
// Shows or hides unread marker view
- (void) configureUnreadMarker
{
if ([self.unreadMarker isHidden] != _inboxMessage.unread)
return;
[self.unreadMarker setHidden:!_inboxMessage.unread];
}
- (void) layoutSubviews
{
[super layoutSubviews];
self.unreadMarker.frame = CGRectMake(4, (self.contentView.bounds.size.height - 6)/2, 6, 6);
self.detailTextLabel.frame = CGRectMake(0, 0, self.contentView.bounds.size.width-10, 20);
if (self.textLabel.text == nil)
{
self.alertLabel.frame = CGRectMake(15, 15, self.contentView.bounds.size.width-20, self.contentView.bounds.size.height-20);
}else
{
self.textLabel.frame = CGRectMake(15, 15, self.contentView.bounds.size.width-20, 20);
self.alertLabel.frame = CGRectMake(15, 35, self.contentView.bounds.size.width-20, self.contentView.bounds.size.height-45);
}
}
// Calculates the height of the cell
+ (CGFloat) heightForCellWithInboxMessage:(R1InboxMessage *) inboxMessage cellWidth:(CGFloat) cellWidth
{
CGFloat height = 25;
if (inboxMessage.title != nil)
height += 20;
if (inboxMessage.alert != nil)
{
if ([inboxMessage.alert respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)])
{
NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
paragraph.lineBreakMode = NSLineBreakByWordWrapping;
NSDictionary *attributes = @{NSFontAttributeName : [UIFont systemFontOfSize:14],
NSParagraphStyleAttributeName: paragraph};
height += [inboxMessage.alert boundingRectWithSize:CGSizeMake(cellWidth-20, 100)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:attributes
context:nil].size.height;
}else
{
height += [inboxMessage.alert sizeWithFont:[UIFont systemFontOfSize:14]
constrainedToSize:CGSizeMake(cellWidth-20, 100)
lineBreakMode:NSLineBreakByWordWrapping].height;
}
height += 1;
}
if (height < 50)
return 50;
return height;
}
@end
R1SampleInboxViewController.h
#import <UIKit/UIKit.h>
#import "R1Inbox.h"
@protocol R1SampleInboxViewControllerDelegate;
@interface R1SampleInboxViewController : UITableViewController <R1InboxMessagesDelegate>
@property (nonatomic, assign) id<R1SampleInboxViewControllerDelegate> inboxDelegate;
- (id) initInboxViewController;
@end
@protocol R1SampleInboxViewControllerDelegate <NSObject>
- (void) sampleInboxViewControllerDidFinished:(R1SampleInboxViewController *) sampleInboxViewController;
@end
R1SampleInboxViewController.m
#import "R1SampleInboxViewController.h"
#import "R1SampleInboxTableViewCell.h"
#import "R1Inbox.h"
@interface R1SampleInboxViewController ()
@property (nonatomic, strong) R1InboxMessages *inboxMessages;
@end
@implementation R1SampleInboxViewController
// Initialize UITableViewController
- (id) initInboxViewController
{
self = [super initWithStyle:UITableViewStylePlain];
if (self)
{
self.inboxMessages = [R1Inbox sharedInstance].messages;
[self updateTitle];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self action:@selector(closeInboxViewController)];
}
return self;
}
// Called when user presses 'Done' button
- (void) closeInboxViewController
{
[self.inboxDelegate sampleInboxViewControllerDidFinished:self];
}
// Updates the title of ViewController with number of unread messages
- (void) updateTitle
{
if (self.inboxMessages.unreadMessagesCount == 0)
self.navigationItem.title = @"Inbox";
else
self.navigationItem.title = [NSString stringWithFormat:@"Inbox (%lu unread)", (unsigned long)self.inboxMessages.unreadMessagesCount];
}
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self updateTitle];
// Add this view controller to the list of delegates when it appears
[self.inboxMessages addDelegate:self];
}
- (void) viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
// Remove this view controller from the list of delegates when it disappears
[self.inboxMessages removeDelegate:self];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Returns the total number of Inbox messages
return self.inboxMessages.messagesCount;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Returns the calculated height of the cell for R1InboxMessage object in row
return [R1SampleInboxTableViewCell heightForCellWithInboxMessage:[self.inboxMessages.messages objectAtIndex:indexPath.row]
cellWidth:self.tableView.frame.size.width];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
R1SampleInboxTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
if (cell == nil)
{
cell = [[R1SampleInboxTableViewCell alloc] initWithReuseIdentifier:@"Cell"];
}
// Sets up the cell for displaying R1InboxMessage object
cell.inboxMessage = [self.inboxMessages.messages objectAtIndex:indexPath.row];
return cell;
}
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
R1InboxMessage *message = [self.inboxMessages.messages objectAtIndex:indexPath.row];
// Shows Inbox message when user interacts to the cell
[[R1Inbox sharedInstance] showMessage:message
messageDidShow:^{
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}];
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewCellEditingStyleDelete;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
R1InboxMessage *message = [self.inboxMessages.messages objectAtIndex:indexPath.row];
[self.inboxMessages deleteMessage:message];
}
#pragma mark - R1InboxMessagesDelegate methods
// This method called when the list of Inbox messages gets updated
- (void) inboxMessagesWillChanged
{
[self.tableView beginUpdates];
}
// This method called when any item in the list of Inbox messages gets changed (modified, inserted or removed)
- (void) inboxMessagesDidChangeMessage:(R1InboxMessage *) inboxMessage
atIndex:(NSUInteger) index
forChangeType:(R1InboxMessagesChangeType)changeType
newIndex:(NSUInteger) newIndex
{
switch (changeType)
{
case R1InboxMessagesChangeInsert:
[self.tableView insertRowsAtIndexPaths:@[ [NSIndexPath indexPathForRow:index inSection:0] ]
withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case R1InboxMessagesChangeDelete:
[self.tableView deleteRowsAtIndexPaths:@[ [NSIndexPath indexPathForRow:index inSection:0] ]
withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case R1InboxMessagesChangeUpdate:
((R1SampleInboxTableViewCell *)[self.tableView cellForRowAtIndexPath: [NSIndexPath indexPathForRow:index inSection:0] ]).inboxMessage = [self.inboxMessages.messages objectAtIndex:index];
break;
default:
break;
}
}
// This method called when the changes to list of Inbox messages are over
- (void) inboxMessagesDidChanged
{
[self.tableView endUpdates];
}
// This method called when the number of unread Inbox messages gets changed
- (void) inboxMessageUnreadCountChanged
{
[self updateTitle];
}
@end
In this example, initialize the R1SampleInboxViewController with the following method:
[[R1SampleInboxViewController alloc] initInboxViewController]
##d. Attribution Tracking Activation ###i. Track RadiumOne Campaigns Please contact your Account Manager to setup R1 ad campaigns as well as tracking campaigns. If you don't have one, please contact us here and one of our Account Managers will assist you.
Once your Account Manager has set up tracking, you will start receiving attribution tracking report automatically!
###ii. Track 3rd party Campaigns
- Please contact your Account Manager to setup tracking URLs for your 3rd party campaigns. If you don't have one, please contact us here and one of our Account Managers will assist you.
- Send the list of all your media suppliers (anyone you run a mobile advertising campaign with).
- For each media supplier, your account manager will send you 2 tracking URLs (one impression tracking URL, one click tracking URL).
- Send each pair of URLs to the relevant Media Supplier so they can set these tracking URLs on the creatives.
- You're all set and will start having access to Attribution Tracking Reports.
##e. Geofencing Activation
#import "R1GeofencingSDK.h"
Geofencing is disabled by default. You can enable it in the application:didFinishLaunchingWithOptions:
method or later.
sdk.geofencingEnabled = YES;
To disable geofencing, either remove the above call or set its value to NO
The R1GeofencingSDK also allows you to notify your users and drive engagements
via local notification. When a user entered
or exited
a region (both
CLGeographicalRegion
and CLBeaconRegion
) you can obtain relevant information
using the NSNotification
object, and the notification object has the following
keys to access the CLRegion
object and its name string respectively:
kR1LocationRegionObjectKey
kR1LocationRegionNameKey
You can register region enter/exit notifications as needed as shown below:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sendLocalEnterNotification:) name:kR1GeofenceDidEnterNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sendLocalExitNotification:) name:kR1GeofenceDidExitNotification object:nil];
In your sendLocalEnterNotification:
and sendLocalExitNotification:
methods,
you can relay your event messages to application:didReceiveLocalNotification:
by overriding it on your application delegate to display these local notifications using your own keys. For example:
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
// you may want to show an alert
NSString *alertString = [notification.userInfo objectForKey:<Your_Own_NotificationAlertBodyKey>];
application.applicationIconBadgeNumber = 0; // reset the badge to zero
NSString *alertTitle = [notification.userInfo objectForKey:<Your__Own_NotificationAlertTypeKey>];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:alertTitle message:alertString delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
[alert show];
}
}
N.B. - The Connect locationService uses the Location Manager in iOS. For deployment on iOS 8 and newer, it is required that the application's property list (plist) file includes one of the two following keys:
NSLocationAlwaysUsageDescription //provide location updates whether the user is actively using the application or not via infrequent background location updates // OR NSLocationWhenInUseUsageDescription //provide location updates only while the user has your application as the foreground running app
These are string values that need to include a description suitable to present to a user. The description should explain the reason the application is requesting access to the user's location. Therefore, description you give should try to incorporate or reference the user benefit that may be possible through sharing location.
#4. Submitting your App
When preparing to send your binary to Apple, you will set up an application target in the iTunes Connect portal (http://itunesconnect.apple.com for details). During this process, you will be presented with the question, "Does this app use the Advertising Identifier (IDFA)?"
![Image of idfaCheck] (Doc_Images/idfaCheck.png)
Your application may or may not be using this value for your own purposes, but the Connect SDK does access it (described below). So, it is required that you answer, "Yes" to the aforementioned question.
If your application is utilizing Connect's analytics, geofencing or push notification features, be sure to check the last use case option - that the application uses the IDFA to "Attribute an action taken within this app to a previously served advertisement" as advertisements that you might have served can be related to users actions within your app.
![Image of idfaAnalyticsOption] (Doc_Images/idfaAnalyticsOption.png)
If your application is also using Connect's Engage (display advertisements) feature, be sure to select the option: "Serve advertisements within the app". Naturally, if your application is only using the Engage functionality, leave all other options unchecked (as related to Connect's use of the IDFA)
![Image of idfaAllOptions] (Doc_Images/idfaAllOptions.png)
You will also need to confirm that your app honors a user's "Limit Ad Tracking" setting in iOS. The Connect SDK does honor this flag and will not access or otherwise utilize the IDFA value if the user has selected the "Limit Ad Tracking" feature. Ensure that this confirmation and the previously mentioned IDFA use options are checked to facilitate a smooth application review process.