iOS Smartwhere Integration

main-logo

The TUNE Marketing Console integration with Smartwhere extends the power of the TUNE platform to the real-world. Enabling it unlocks features such as:

  • In-store visit attribution.
  • Audience insights and segment creation based on the real-world places your app users visit and the length of their visit.
  • Proximity triggered engagements.

Smartwhere works with all existing proximity technologies, from Beacons, Geofencing and Wi-Fi, NFC and QR. Smartwhere is already helping:

  • Retail Campaigns
  • In-Store Experiences
  • Museums
  • On-site Mapping and Blue Dot
  • Events and Venues
  • Integrated Print Campaigns
  • Social Media Campaigns

The following document will walk you through the steps necessary to enable Smartwhere for your iOS application.

Note: Before beginning your Smartwhere integration be sure that your TUNE account has had the Smartwhere functionality enabled. Please contact your Customer Success Manager for more information.

iOS Integration

To integrate Smartwhere Proximity and Location functionality with your iOS app follow these steps:

Step 1: Integrate the TUNE SDK v 4.13.3

Follow the TUNE iOS Quick Start Guide for detailed instructions.

Step 2: Configure your app project

Include both of the following frameworks within your app project:

  • Core.Location
  • Core.Bluetooth

In addition, make sure that NSLocationAlwaysUsageDescription, i.e. "Privacy - Location Always Usage Description" entry exists in the Info.plist.

The value of this key should include a user-visible message about why your app needs to access the user's geo-location, e.g. "Enable location to get great offers!". All users will see this value.

Step 3: Enable Smartwhere

Smartwhere is included in the latest TUNE SDK but is disabled by default. To enable Smartwhere call the following method after the TUNE initialization:

[Tune enableSmartwhereIntegration];

To optionally expose your TUNE client events to Smartwhere, for example to trigger proximity engagements based on your own custom events, make the following call after enabling Smartwhere:

[Tune configureSmartwhereIntegrationWithOptions:TuneSmartwhereShareEventData];

Step 4: Request location permissions

Request the end-user permission for location access, at the earliest appropriate instance in your app flow. In addition to being a requirement for enabling Smartwhere this will also allow latitude and longitude to be collected when TUNE measureSession/measureEvent methods are called.

Step 5: Configure Notification Handling

Smartwhere allows you to trigger device notifications based on variety of proximity events. For this functionality to work your app must support notifications. The specific implementation will depend upon your supported version of iOS.

iOS 10 or higher

Request notification permissions using the User Notification Center. If you have implemented TUNE In-App Messaging (IAM) no further steps are necessary; proximity notifications will automatically appear if the app is in the foreground or background.

If you are not using TUNE IAM, you can manually handle proximity generated notifications within either the willPresentNotification or didReceiveNotificationResponse event handlers of the Notification Center. The content of the proximity notifications can be accessed via the didReceiveNotificationResponse method of the Smartwhere object, as demonstrated in the following code:

-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler{
    SmartWhere* smartWhere = [[TuneSmartWhereHelper getInstance] getSmartWhere];
    if (smartWhere) {
        [smartWhere didReceiveNotificationResponse:response];
    }
    NSLog(@"User Info : %@",response.notification.request.content.userInfo);
    completionHandler();
}

Swift Integration

Here is the swift integration code for Smartwhere:

class AppDelegate...{
let Tune_Advertiser_Id   = "adv_id"
let Tune_Conversion_Key  = "conversion_key" 
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
       Tune.initialize(withTuneAdvertiserId: Tune_Advertiser_Id, tuneConversionKey: Tune_Conversion_Key)
       Tune.setDelegate(self)
       Tune.enableSmartwhereIntegration()
       Tune.configureSmartwhereIntegration(withOptions: Int(TuneSmartwhereShareEventData.rawValue))
       return true
    }
}

For the UNNotificationServiceExtension code, you need to create a new Service Extension file. Select File -> New -> Target -> Notification Service Extension. Once you have the Service Extension, use the following code example:

#import "NotificationService.h"

@interface NotificationService ()

@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;

@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    NSLog(@"Rich notification get!!");
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    NSLog(@"recieved contents %@", request.content.userInfo);
    
    // Modify the notification content here...
    if (self.bestAttemptContent.title) {
        NSLog(@"Modify title");
        self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
    }
    
    NSDictionary *userInfo = request.content.userInfo;
    NSArray *tuneAttachments = userInfo[@"tune_attachments"];
    NSMutableArray<NSURL *> *urls = [NSMutableArray arrayWithCapacity:[tuneAttachments count]];
    NSLog(@"attachments are %@", tuneAttachments);
    
    for (id url in tuneAttachments) {
        NSLog(@"url %@", url);
        [urls addObject:[NSURL URLWithString:url]];
    }

    [self loadWithUrls:urls completeHandler:^(NSArray * _Nullable attachments, NSError * _Nullable error) {
        if (error) {
            self.contentHandler(self.bestAttemptContent);
            return;
        }
        
        // Set attachments of notification content
        NSLog(@"here %@", attachments);
        self.bestAttemptContent.attachments = attachments;
        self.contentHandler(self.bestAttemptContent);
        
    }];
}

- (void) loadWithUrls:(NSArray<NSURL *> * _Nonnull) urls completeHandler: (void (^)(NSArray * _Nullable attachments, NSError * _Nullable error)) handler {
    NSInteger size = [urls count];
    NSLog(@"loadWithUrls %ld", (long) size);
    NSURLSession * session = [NSURLSession sharedSession];
    NSMutableArray<UNNotificationAttachment *>* mutableAttachments = [NSMutableArray arrayWithCapacity:size];
    NSMutableArray<NSError *>* errors = [NSMutableArray arrayWithCapacity:size];
    
    NSLog(@"dispatch async");
    for (NSURL * url in urls) {
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        NSURLSessionDataTask * task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            
            NSLog(@"url in loadWithUrls %@", url);
            if (error) {
                [errors addObject:error];
                dispatch_semaphore_signal(semaphore);
                return;
            }
            
            NSArray *cachePaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
            NSString *cachesFolder = cachePaths[0];
            NSString *uniqueID = [[NSProcessInfo processInfo] globallyUniqueString];
            NSString * nameAndExtension = [url lastPathComponent];
            NSString *fileName = [uniqueID stringByAppendingString:nameAndExtension];
            NSLog(@"name: %@", nameAndExtension);
            NSString *fullPath = [cachesFolder stringByAppendingPathComponent:fileName];
            NSError *writeError = nil;
            [data writeToFile:fullPath options:NSDataWritingAtomic error:&writeError];
            
            if (writeError != nil) {
                [errors addObject:writeError];
                dispatch_semaphore_signal(semaphore);
                return;
            }
            
            // Create attachment object from file
            NSError *attachError = nil;
            NSURL *attachURL = [NSURL fileURLWithPath:fullPath isDirectory:NO];
            UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:fileName URL:attachURL options:nil error:&attachError];
            if (attachError) {
                [errors addObject:writeError];
                dispatch_semaphore_signal(semaphore);
                return;
            }
            
            [mutableAttachments addObject:attachment];
            NSLog(@"signal semaphore");
            dispatch_semaphore_signal(semaphore);
        }];
        [task resume];
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        if ([errors count] > 0) {
            NSError *error = [errors objectAtIndex:0];
            handler(nil, error);
            return;
        }
    }
    
    NSArray<UNNotificationAttachment *> * attachments = [NSArray arrayWithArray:mutableAttachments];
    handler(attachments, nil);
}


- (void)serviceExtensionTimeWillExpire {
    // Called just before the extension will be terminated by the system.
    // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
    self.contentHandler(self.bestAttemptContent);
    
    NSLog(@"ran outta time!");
}

@end

iOS 9 or below
Register for notification permissions using the registerUserNotificationsSettings method. Within the application didReceiveLocalNotification event handler call the Smartwhere didReceiveLocalNotificationSW method. If the app is in the background when the notification is triggered this method will allow the associated notification action to be executed when clicked. If the app is in the foreground, it will return a swNotification object. This object can then be parsed for proximity notification specific information and/or passed to an additional event handler, such as the Smartwhere didReceiveLocalNotification handler for custom management of the notification while the app is in the foreground as shown in the following sample code:

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
    SmartWhere* smartWhere = [[TuneSmartWhereHelper getInstance] getSmartWhere];

    if (smartWhere) {
        SWNotification* swNotification = [smartWhere didReceiveLocalNotificationSW:notification];
        if (swNotification) {
            [self smartWhere:smartWhere didReceiveLocalNotification:swNotification];
        }
    }
}


- (void)smartWhere:(SmartWhere *)smartwhere didReceiveLocalNotification:(SWNotification *)notification {
    NSLog(@"SWNotification came in while in the foreground, alerting the user");
    _lastEvent = notification;
    
    /* display notification with a simple UIAlertController */
    UIAlertController *alertcontroller = [UIAlertController alertControllerWithTitle: notification.title
                                                                             message: notification.message
                                                                      preferredStyle: UIAlertControllerStyleAlert];
    UIAlertAction *okAction = [UIAlertAction actionWithTitle: @"Ok"
                                                       style: UIAlertActionStyleDefault
                                                     handler: ^(UIAlertAction *action) {
                                                         [smartwhere fireLocalNotificationAction:_lastEvent];
                                                         _lastEvent = nil;
                                                     }];
    [alertcontroller addAction: okAction];
    UIAlertAction *cancel = [UIAlertAction actionWithTitle: @"Cancel" style: UIAlertActionStyleDestructive handler: nil];
    
    [alertcontroller addAction: cancel];
    
    UINavigationController *navigationController = (UINavigationController *) self.window.rootViewController;
    
    [self.window setRootViewController:navigationController];
    [navigationController presentViewController:alertcontroller animated:YES completion:nil];
}

Disabling Tune Smartwhere Integration

If for any reason you need to disable TUNE Smartwhere SDK integration post app initialization, use the following call:

[Tune disableSmartwhereIntegration];

iOS Sample Code

The following sample code demonstrates how to integrate and enable Smartwhere with TUNE within your app.

Initialization

CLLocationManager initialized in UIAppDelegate.applicationDidFinishLaunching: before Tune.init is called:

@import AdSupport;
@import CoreLocation;
@import CoreSpotlight;
@import CoreTelephony;
@import Tune;
@import MobileCoreServices;
@import iAd;
@import Security;
@import StoreKit;
@import SystemConfiguration;

@interface AppDelegate () <TuneDelegate, CLLocationManagerDelegate> {
    CLLocationManager *locationManager;
}
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    // REQUIRED: request location access permission here or at some other location in your code
    locationManager = [CLLocationManager new];
    //locationManager.delegate = self;
    [locationManager requestAlwaysAuthorization];
    [locationManager startUpdatingLocation];

    // REQUIRED: initialize Tune
    [Tune initializeWithTuneAdvertiserId:TUNE_ADVERTISER_ID
                       tuneConversionKey:TUNE_CONVERSION_KEY
                         tunePackageName:TUNE_PACKAGE_NAME
                                wearable:NO];

[Tune enableSmartwhereIntegration];
[Tune configureSmartwhereIntegrationWithOptions:TuneSmartwhereShareEventData];

    // OPTIONAL: set a delegate for Tune (and implement the TuneDelegate protocol)
    [Tune setDelegate:self];

    return YES;
}

@end

Please make sure of the following during SDK implementation:

  • SmartWhere framework - iOS system frameworks dependency requires CoreBluetooth and CoreLocation frameworks.
  • Request the end-user permission for geo-location access, at the earliest appropriate instance in your app flow. This allows the SmartWhere SDK to handle geofence transitions and also allows (latitude, longitude) to be collected when TUNE measureSession/measureEvent methods are called.
  • Please make sure that NSLocationAlwaysUsageDescription, i.e. "Privacy - Location Always Usage Description" entry exists in the Info.plist. The value of this key should include a user-visible message about why your app needs to access the user's geo-location, e.g. "Please allow geo-location access to get location based offers!"

iOS Debug Messages

When debugMode is enabled, the console log statements will additionally include messages (prefaced with “sw_tune”) related to location proximity events such as the following:

sw_tune_2[33005:3181625] TUNE SDK - [TuneSmartWhereHelper.m:65] - WARN - TUNE: Starting SmartWhere Proximity Monitoring
sw_tune_2[33005:3181625] ProximityService version: REL_17087.1
sw_tune_2[33005:3181625] configWithDictionary start
sw_tune_2[33005:3181625] configWithDictionary complete
sw_tune_2[33005:3181625] getLocationManager: startMonitoringSignificantLocationChanges
sw_tune_2[33005:3181625] serverOverrides start
sw_tune_2[33005:3181625] serverOverrides complete
sw_tune_2[33005:3181625] Setting DEBUG_LOGGING to YES...
...
sw_tune_2[33005:3181625] Got 877 beacons: (
    "2f236764-cf8f-4ab1-cdf5-f4933ba9eea8"
)
sw_tune_2[33005:3181625] Setting RANGE_IN_ENTER_THRESHOLD to 1
sw_tune_2[33005:3181625] Setting RANGE_INSIDE_EXIT_THRESHOLD to 1
sw_tune_2[33005:3181625] Beacons ready for monitoring
sw_tune_2[33005:3181625] Starting location manager.  Monitoring: {(
)}
...
sw_tune_2[33005:3181625] Start watching beacon: 2f236764-cf8f-4ab1-cdf5-f4933ba9eea8
sw_tune_2[33005:3181625] WARNING: Ranging beacons is not available
...
sw_tune_2[33005:3181625] getFencesForLocation: Got 6 fences for application 877
sw_tune_2[33005:3181625] getFencesForLocation: Watched fences doesn't exist yet.
sw_tune_2[33005:3181625] startWatchingFences: Start watching fence: 1032543 Sydney 7K <-33.868820,151.208268,7000.000000>
sw_tune_2[33005:3181625] startWatchingFences: Start watching fence: 0 Refresh Geofence <-33.863400,151.211000,5000.000000>
...
...
sw_tune_2[33005:3181625] SWEventsObject: enter: Created visit id 74C7A139-0FFF-4612-8612-93A9B0A50327 for 1032543
sw_tune_2[33005:3181625] addTrackingObject type 5 for item 1032543 with trigger 20
sw_tune_2[33005:3181625] Sending local notification Sydney 7K Entered
Entered event
...
sw_tune_2[33005:3181625] addTrackingObject type 2 for item 1032543 with trigger 20
...
sw_tune_2[33005:3181625] didUpdateLocations: stopUpdatingLocation
...

iOS Troubleshooting Common Issues

  • Make sure CoreLocation and CoreBluetooth frameworks are included.
  • Make sure location access permission is granted when the dialog shows up. This permission can also be modified from the system Settings.
  • Make sure appropriate geofences, beacons, etc. have been setup on SmartWhere.
  • Check the TUNE TMC AA dashboard "Test Logs" report since this report can show near real time log records.

No Comments

Leave a reply