iOS

Getting started

1. Install the SDK

You can install the Localytics iOS SDK through Cocoapods, Carthage, or manually.

If you are upgrading from a previous version of the SDK, please refer to documentation for upgrading the SDK.

Cocoapods

The easiest way to use Localytics in your project is with CocoaPods.

  1. Install CocoaPods by executing
    sudo gem install cocoapods
    
  2. If you don't already have a Podfile in your Xcode project directory, create one with the following command
    pod init
    
  3. In your Podfile add the following content
    platform :ios, '9.0'
    pod 'Localytics', '~> 6.2.1'
    
  4. Install the Localytics SDK by executing the following in your Xcode project directory
    pod install
    
  5. Open the project workspace file (YOUR-PROJECT-NAME.xcworkspace) instead of the project file (YOUR-PROJECT-NAME.xcodeproj) to ensure that the Localytics dependency is properly loaded.

Carthage

Localytics also supports Carthage. To install Carthage and create a Cartfile, check out the instructions on the Carthage website.

Add the following to your Cartfile:

binary "https://downloads.localytics.com/SDKs/iOS/Localytics.json" ~> 6.2.1

Follow the normal steps to add the Localytics framework to your project. They consist of running carthage update, linking the Localytics framework in your project settings, and finally adding a build phase to copy the framework to the correct path.

Finally, add AdSupport.framework, libsqlite3.tbd, libz.tbd, CoreLocation.framework, and SystemConfiguration.framework to your project unless you already have them. You can add them by going to Linked Frameworks and Libraries in the General tab of your project settings within Xcode..

Manual Installation

If you wish to install the SDK without using CocoaPods or Carthage, the following manual approach will install the SDK and its dependencies.

  1. Delete any existing Localytics files from your project.
  2. Add AdSupport.framework, libsqlite3.tbd, libz.tbd, CoreLocation.framework, and SystemConfiguration.framework to your project unless you already have them. You can add them by going to Linked Frameworks and Libraries in the General tab of your project settings within Xcode.
  3. Download the latest version of the SDK.
  4. XCFramework Installation

  5. Check if your Xcode project contains a “Frameworks” folder in project navigator. If it doesn’t, just like in the screenshot below, then we’ll have to create one. The Frameworks folder is not added by default in latest Xcode and it’s a good practice to keep your frameworks there.
  6. Create Frameworks folder

  7. Right click on your project in the project navigator (top-most entry) , and select “New Group”. Name the new group Frameworks.
  8. Add XCFramework to Frameworks folder

  9. Drag and drop it from Finder into the Frameworks folder. Make sure that the destination of drag is just under the Frameworks folder:
  10. Both “Copy items if needed” and “Create groups” should be checked and selected.
  11. Embed Localytics XCFramework in project’s target

  12. Unzip it and drag Localytics.framework into the Embedded Binaries section of the General tab of your Xcode project settings as shown below. Check the Copy items if needed box.
  13. When submitting an iOS app build to iTunes Connect for distribution you'll have to remove the simulator-compatible (x86) version of all frameworks your app uses, including Localytics.

    Rather than shipping a simulator-incompatible library or forcing our users to swap to a "skinny" version of our library at build time, the easiest way to ship without the simulator architecture is to add a script to your build process that removes the "fat" architectures used by the simulator.

    Configure framework slices in Project Settings
    1. Enter Project Naviator view and click on your project icon. It should be at the top of the Project Navigator.
    2. Select your target from the sidebar or from the dropdown menu, then select the Build Phases tab.
    3. Press the + in the top left and add a new run script phase after the embed frameworks step.
    4. Inside the Run Script phase make sure that the Shell is /bin/sh and insert the following code:
      APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
      # This script loops through the frameworks embedded in the application and
      # removes unused architectures.
      find "$APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK
      
      do
        FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable)
        FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"
      
        echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"
        EXTRACTED_ARCHS=()
      
        echo "ARCHS = $ARCHS"
      
        INFO_OUTPUT_STR=`lipo -info "$FRAMEWORK_EXECUTABLE_PATH"`
        if [[ $INFO_OUTPUT_STR == *"Non-fat file"* ]]
        then
          echo "Framework is not a Fat binary, skipping..."
          continue
        fi
      
        for ARCH in $ARCHS
        do
          echo "Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME"
          lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"
          EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")
        done
      
        echo "Merging extracted architectures: ${ARCHS}"
        lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"
        rm "${EXTRACTED_ARCHS[@]}"
      
        echo "Replacing original executable with thinned version"
        rm "$FRAMEWORK_EXECUTABLE_PATH"
        mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"
      done
          
      

2. Configure test mode

We strongly recommend that you set up test mode. It's important for other users of your Localytics instance to test marketing campaigns—like push, in-app, and inbox messages—before and after the app is released. It's also valuable for future troubleshooting from Localytics.

To configure test mode, you'll need to setup a URL scheme. The URL scheme is defined as "amp" followed by your Localytics app key, as demonstrated below. You can find your app key in the Localytics Dashboard. If you switch between multiple Localytics app keys, as when you have separate testing and production app keys, there's no harm in registering both URL schemes.

Configure test mode URL scheme in Project Settings
  1. Enter Project Navigator view and click on your project icon. It should be at the top of the Project Navigator.
  2. Select your target from the sidebar or from the dropdown menu, then select the Info tab.
  3. Open the URL Types expander and click +.
  4. In the URL Schemes field, enter "amp" followed by your Localytics app key.

3. Initialize the SDK

In order to run the Localytics SDK, you must initialize the SDK using your Localytics app key. You can find your app key in the Localytics Dashboard. Localytics can be used in Objective-C, Swift, and mixed language projects. More information on using Localytics within a Swift project is available here.

If you have an app with extensive, engaged usage in the background, or you require more fine control over the session lifecycle (most apps don't), use manual integration.

In your application's delegate file:

  1. Import the Localytics SDK under any existing imports.

    Objective-C Swift

    @import Localytics;
    
    import Localytics
    
  2. For customers who grant their users the ability to opt out of data collection, please continue with integration by following the advanced section.

    Additionally, for customers who opt their app into iPad multi-window behavior in iOS 13 and above, please continue with integration by following the advanced section.

    Add the following line to the start of didFinishLaunchingWithOptions:.

    Objective-C Swift

    [Localytics autoIntegrate:@"YOUR-LOCALYTICS-APP-KEY"
        withLocalyticsOptions:@{
                                LOCALYTICS_WIFI_UPLOAD_INTERVAL_SECONDS: @5,
                                LOCALYTICS_GREAT_NETWORK_UPLOAD_INTERVAL_SECONDS: @10,
                                LOCALYTICS_DECENT_NETWORK_UPLOAD_INTERVAL_SECONDS: @30,
                                LOCALYTICS_BAD_NETWORK_UPLOAD_INTERVAL_SECONDS: @90
                              }
                launchOptions:launchOptions];
    
    Localytics.autoIntegrate("YOUR-LOCALYTICS-APP-KEY", withLocalyticsOptions:[
                            LOCALYTICS_WIFI_UPLOAD_INTERVAL_SECONDS: 5,
                            LOCALYTICS_GREAT_NETWORK_UPLOAD_INTERVAL_SECONDS: 10,
                            LOCALYTICS_DECENT_NETWORK_UPLOAD_INTERVAL_SECONDS: 30,
                            LOCALYTICS_BAD_NETWORK_UPLOAD_INTERVAL_SECONDS: 90
                          ], launchOptions: launchOptions)
    

    Localytics attempts to upload user data quickly to our backend to power time sensitive messaging use cases. By default, Localytics will upload data periodically based on the state of a user's network connection. However, you have full flexibility over this behavior. While not recommended, you can change the upload intervals for each type of connection, and even remove this type of behavior entirely and depend on your own Localytics.upload() calls to upload data whenever you wish.

    To use the default intervals provided by Localytics, you can pass in nil into localyticsOptions. If you would like to disable scheduled uploads, pass in -1 as the value for all keys.

    The available keys for setting upload intervals are:

    • LOCALYTICS_WIFI_UPLOAD_INTERVAL_SECONDS: Defines the interval that will be used in the case of a WiFi connection. Having a WiFi connection will supersede any mobile data connection. Default value is 5 seconds.
    • LOCALYTICS_GREAT_NETWORK_UPLOAD_INTERVAL_SECONDS: Defines the interval that will be used in the case of 4G or LTE connections. Default value is 10 seconds.
    • LOCALYTICS_DECENT_NETWORK_UPLOAD_INTERVAL_SECONDS: Defines the interval that will be used in the case of 3G connection. Default value is 30 seconds.
    • LOCALYTICS_BAD_NETWORK_UPLOAD_INTERVAL_SECONDS: Defines the interval that will be used in the case of 2G or EDGE connections. Default value is 90 seconds.
  3. Compile and run your app.

4. Next steps

Congratulations! You have successfully performed the basic Localytics integration and are now sending session data to Localytics. You can also use Localytics In-App Messaging to message users in your app, and you have everything you need to track where your most highly engaged users are coming from.

Note that it may take a few minutes for your first datapoints to show up within the Localytics Dashboard. In the meantime, we suggest reading the next few sections to learn how to:

  1. Track one user action as an event
  2. Track one user property as a profile attribute
  3. Integrate push messaging

We recommend doing these things before releasing your app for the first time with Localytics.

Session lifecycle

With just the basic setup above, the Localytics SDK automatically tracks user engagement and retention by tracking patterns of foregrounding and backgrounding of your app. Upon foregrounding, the Localytics SDK automatically creates and uploads a "start session" datapoint that captures many details about the user's device (e.g., device model, OS version, device IDs) and is used for powering charts within Localytics.

Upon backgrounding, the SDK marks the current time. When the user returns to the app later and it has been more than 15 seconds (or a manually set session timeout) since the user had last backgrounded the app, the SDK will close the previous session by creating a "close session" datapoint, create a new "start session" datapoint, and upload both of these datapoints. If the user foregrounds the app within the session timeout of the previous backgrounding, the previous session is resumed as if the user had not left the app at all. Due to this automatic session lifecycle tracking, Localytics is able to derive session length, session interval, session counts, session trending, and a number of other core metrics for exploration in the Localytics Dashboard.

Whenever the app transitions to the foreground or background, the Localytics SDK attempts to upload any datapoints which are cached on the device. Uploads are performed in batches to reduce network use and increase the likelihood of successful uploads. Data remains on the device until it is successfully uploaded, and only then does the SDK remove it from the device.

Starting in SDK v5.0, the Localytics SDK also will attempt to upload any datapoints periodically using set intervals based on a user's network connection.

Tagging events

Track user actions in your app using events in Localytics. All events must have a name, but you can also track the details of the action with event attributes. Event attributes help to provide context about why and how the action occurred. Every event can have up to 50 attributes unique to that event with each attribute having a limit of 255 characters.

Standard events

Standard events make it easier to analyze user behavior and optimize your app marketing around common business goals such as driving user registrations or purchases. You can also tag custom events for other user behavior in your app that doesn't match one of the standard events.

Purchased

Objective-C Swift

[Localytics tagPurchased:@"Shirt"
                  itemId:@"sku-123"
                itemType:@"Apparel"
               itemPrice:@15
              attributes:extraAttributes];
Localytics.tagPurchased("Shirt", itemId: "sku-123", itemType: "Apparel", itemPrice: 15, attributes: extraAttributes)

Added to Cart

Objective-C Swift

[Localytics tagAddedToCart:@"Shirt"
                    itemId:@"sku-123"
                  itemType:@"Apparel"
                 itemPrice:@15
                attributes:extraAttributes];
Localytics.tagAddedToCart("Shirt", itemId: "sku-123", itemType: "Apparel", itemPrice: 15, attributes: extraAttributes)

Started Checkout

Objective-C Swift

[Localytics tagStartedCheckout:@50
                     itemCount:@2
                    attributes:extraAttributes];
Localytics.tagStartedCheckout(50, itemCount: 2, attributes: extraAttributes)

Completed Checkout

Objective-C Swift

[Localytics tagCompletedCheckout:@50
                       itemCount:@2
                      attributes:extraAttributes];
Localytics.tagCompletedCheckout(50, itemCount: 2, attributes: extraAttributes)

Content Viewed

Objective-C Swift

[Localytics tagContentViewed:@"Top 10"
                   contentId:@"e8z7319zbe"
                 contentType:@"Article"
                  attributes:extraAttributes];
Localytics.tagContentViewed("Top 10", contentId: "e8z7319zbe", contentType: "Article", attributes: extraAttributes)

Searched

Objective-C Swift

[Localytics tagSearched:@"Celtics"
            contentType:@"Sports"
            resultCount:@15
             attributes:extraAttributes];
Localytics.tagSearched("Celtics", contentType: "Sports", resultCount: 15, attributes: extraAttributes)

Shared

Objective-C Swift

[Localytics tagShared:@"Top 10"
            contentId:@"e8z7319zbe"
          contentType:@"Article"
           methodName:@"Twitter"
           attributes:extraAttributes];
Localytics.tagShared("Top 10", contentId: "e8z7319zbe", contentType: "Article", methodName: "Twitter", attributes: extraAttributes)

Content Rated

Objective-C Swift

[Localytics tagContentRated:@"Headlines"
                  contentId:@"8a4z5j9q"
                contentType:@"Song"
                     rating:@5
                 attributes:extraAttributes];
Localytics.tagContentRated("Headlines", contentId: "8a4z5j9q", contentType: "Song", rating: 5, attributes: extraAttributes)

Customer Registered

The LLCustomer parameter is optional - you can pass in nil. However, if you do choose to include an LLCustomer object, the appropriate identifying users properties will be automatically set.

Objective-C Swift

[Localytics tagCustomerRegistered:[LLCustomer customerWithBlock:^(LLCustomerBuilder *builder) {
          builder.customerId = @"3neRKTxbNWYKM4NJ";
          builder.firstName = @"John";
          builder.lastName = @"Smith";
          builder.fullName = @"Mr. John Smith, III";
          builder.emailAddress = @"john@smith.com";
      }]
                       methodName:@"Facebook"
                       attributes:extraAttributes];
Localytics.tagCustomerRegistered((LLCustomer { (builder) in
  builder.customerId = "3neRKTxbNWYKM4NJ"
  builder.firstName = "John"
  builder.lastName = "Smith"
  builder.fullName = "Mr. John Smith, III"
  builder.emailAddress = "john@smith.com"
}), methodName: "Facebook", attributes: extraAttributes)

Customer Logged In

The LLCustomer parameter is optional - you can pass in nil. However, if you do choose to include an LLCustomer object, the appropriate identifying users properties will be automatically set.

Objective-C Swift

[Localytics tagCustomerLoggedIn:[LLCustomer customerWithBlock:^(LLCustomerBuilder *builder) {
          builder.customerId = @"3neRKTxbNWYKM4NJ";
          builder.firstName = @"John";
          builder.lastName = @"Smith";
          builder.fullName = @"Mr. John Smith, III";
          builder.emailAddress = @"john@smith.com";
      }]
                     methodName:@"Native"
                     attributes:extraAttributes];
Localytics.tagCustomerLoggedIn((LLCustomer { (builder) in
  builder.customerId = "3neRKTxbNWYKM4NJ"
  builder.firstName = "John"
  builder.lastName = "Smith"
  builder.fullName = "Mr. John Smith, III"
  builder.emailAddress = "john@smith.com"
}), methodName: "Native", attributes: extraAttributes)

Customer Logged Out

Objective-C Swift

[Localytics tagCustomerLoggedOut:extraAttributes];
Localytics.tagCustomerLoggedOut(extraAttributes)

Invited

Objective-C Swift

[Localytics tagInvited:@"SMS"
            attributes:extraAttributes];
Localytics.tagInvited("SMS", attributes: extraAttributes)

Custom event

Objective-C Swift

[Localytics tagEvent:@"Team Favorited"];
Localytics.tagEvent("Team Favorited")

Custom event with attributes

Objective-C Swift

[Localytics tagEvent:@"Team Favorited"
          attributes:@{@"Team Name" : @"Celtics", @"City" : @"Boston"}];
Localytics.tagEvent("Team Favorited", attributes: ["Team Name" : "Celtics", "City" : "Boston"])

Identifying users

The Localytics SDK automatically captures and uploads device IDs which the Localytics backend uses to uniquely identify users. Some apps connect to their own backend systems that use different IDs for uniquely identifying users. There is often additional identifying information, such as name and email address, connected with the external IDs. Localytics provides various setters for passing this information to Localytics when it is available in your app. Using these setters ensures that you will be able to properly connect Localytics IDs to the IDs available in other systems.

For customers who grant their users the ability to opt out of data collection, please follow the log in and log out flows mentioned in the advanced section.

To easily identify your users during your login and/or registration flow, use our customer registered and customer logged in standard events.

Customer ID

Objective-C Swift

[Localytics setCustomerId:@"3neRKTxbNWYKM4NJ"];
Localytics.setCustomerId("3neRKTxbNWYKM4NJ")

Customer first name

Objective-C Swift

[Localytics setCustomerFirstName:@"John"];
Localytics.setCustomerFirstName("John")

Customer last name

Objective-C Swift

[Localytics setCustomerLastName:@"Smith"];
Localytics.setCustomerLastName("Smith")

Customer full name

Objective-C Swift

[Localytics setCustomerFullName:@"Sir John Smith, III"];
Localytics.setCustomerFullName("Sir John Smith, III")

Customer email address

Objective-C Swift

[Localytics setCustomerEmail:@"sir.john@smith.com"];
Localytics.setCustomerEmail("sir.john@smith.com")

User profiles

Track user properties using profile attributes in Localytics. Each profile has one or more named properties that describe that user. Because they contain rich user data, profiles are excellent for creating audiences to target with personalized messaging. Each profile is identified by a unique user ID that you provide to Localytics via the SDK. If you do not set a known user ID, then the Localytics SDK automatically generates an anonymous profile ID.

Each time you set the value of a profile attribute, you can set the scope to "app-level" or "org-level". App-level profile attributes are only stored in relation to that specific Localytics app key, so they can only be used for building audiences for that one app. Org-level profile attributes are available to all apps in the org, so they can be used across multiple Localytics app keys, which might represent the same app on a different platform or multiple apps produced by your company. If you choose not to set a scope, the SDK defaults to "app-level" scope.

If you repeatedly set the same profile attribute value, the Localytics SDK and backend will take care of deduplicating the values for you so only the most recent value gets stored for that profile.

Profile names must be NSStrings and cannot be nil, empty strings, or begin with an underscore ("_"). Property values must be either single instances or an NSArray of: NSString, NSNumber, or NSDate. Passing in nil or NSNull will delete the value.

Setting a profile attribute value

Numeric value

Objective-C Swift

[Localytics setValue:@45 forProfileAttribute:@"Age" withScope:LLProfileScopeOrganization];
Localytics.setValue(45, forProfileAttribute: "Age", with: .organization)

Numeric values in a set

Objective-C Swift

[Localytics setValue:@[@8, @13] forProfileAttribute:@"Lucky Numbers" withScope:LLProfileScopeApplication];
Localytics.setValue([8, 13], forProfileAttribute: "Lucky numbers", with: .application)

Date value

Objective-C Swift

[Localytics setValue:[NSDate date] forProfileAttribute:@"Last Purchase Date" withScope:LLProfileScopeOrganization];
Localytics.setValue(NSDate(), forProfileAttribute: "Last Purchase Date", with: .organization)

Date values in a set

Objective-C Swift

NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"MM-dd-yyyy"];
[Localytics setValue:@[[dateFormatter dateFromString:@"10-01-2015"], [dateFormatter dateFromString:@"03-17-2016"]] forProfileAttribute:@"Upcoming Milestone Dates" withScope:LLProfileScopeApplication];
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy"
Localytics.setValue([dateFormatter.date(from: "10-01-2015")!, dateFormatter.date(from: "03-17-2016")!], forProfileAttribute: "Upcoming Milestone Dates", with: .application)

String value

Objective-C Swift

[Localytics setValue:@"New York, New York" forProfileAttribute:@"Hometown" withScope:LLProfileScopeOrganization];
Localytics.setValue("New York, New York", forProfileAttribute: "Hometown", with: .organization)

String values in a set

Objective-C Swift

[Localytics setValue:@[@"New York", @"California", @"South Dakota"] forProfileAttribute:@"States Visited" withScope:LLProfileScopeApplication];
Localytics.setValue(["New York", "California", "South Dakota"], forProfileAttribute: "States Visited", with: .application)

Removing a profile attribute

Objective-C Swift

[Localytics deleteProfileAttribute:@"Days Until Graduation" withScope:LLProfileScopeApplication];
Localytics.deleteProfileAttribute("Days Until Graduation", with: .application)

Adding to a set of profile attribute values

Adding a numeric value to a set

Objective-C Swift

[Localytics addValues:@[@8] toSetForProfileAttribute:@"Lucky Numbers" withScope:LLProfileScopeApplication];
Localytics.addValues([8], toSetForProfileAttribute: "Lucky Numbers", with: .application)

Adding a date value to a set

Objective-C Swift

NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"MM-dd-yyyy"];
[Localytics addValues:@[[dateFormatter dateFromString:@"04-19-2015"], [dateFormatter dateFromString:@"12-24-2015"]] toSetForProfileAttribute:@"Upcoming Milestone Dates" withScope:LLProfileScopeApplication];
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy"
Localytics.addValues([dateFormatter.date(from: "4-19-2015")!, dateFormatter.date(from: "12-24-2015")!], toSetForProfileAttribute: "Upcoming Milestone Dates", with: .application)

Adding a string value to a set

Objective-C Swift

[Localytics addValues:@[@"North Dakota"] toSetForProfileAttribute:@"States Visited" withScope:LLProfileScopeApplication];
Localytics.addValues(["North Dakota"], toSetForProfileAttribute: "States Visited", with: .application)

Removing from a set of profile attribute values

Removing numeric values from a set

Objective-C Swift

[Localytics removeValues:@[@8, @9] fromSetForProfileAttribute:@"Lucky Numbers" withScope:LLProfileScopeApplication];
Localytics.removeValues([8, 9], fromSetForProfileAttribute: "Lucky Numbers", with: .application)

Removing date values from a set

Objective-C Swift

NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"MM-dd-yyyy"];
[Localytics removeValues:@[[dateFormatter dateFromString:@"03-17-2016"]] fromSetForProfileAttribute:@"Upcoming Milestone Dates" withScope:LLProfileScopeApplication];
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy"
Localytics.removeValues([dateFormatter.date(from: "03-17-2016")!], fromSetForProfileAttribute: "Upcoming Milestone Dates", with: .application)

Removing string values from a set

Objective-C Swift

[Localytics removeValues:@[@"California"] fromSetForProfileAttribute:@"States Visited" withScope:LLProfileScopeApplication];
Localytics.removeValues(["California"], fromSetForProfileAttribute: "States Visited", with: .application)

Incrementing a numeric profile attribute value

Objective-C Swift

[Localytics incrementValueBy:1 forProfileAttribute:@"Age" withScope:LLProfileScopeOrganization];
Localytics.incrementValueBy(1, forProfileAttribute: "Age", with: .organization)

Decrementing a numeric profile attribute value

Objective-C Swift

[Localytics decrementValueBy:3 forProfileAttribute:@"Days Until Graduation" withScope:LLProfileScopeApplication];
Localytics.decrementValueBy(3, forProfileAttribute: "Days Until Graduation", with: .application)

In-app messaging

In-app messaging allows you to engage with your users while they are inside your app using templated or custom HTML creatives that you define within the Localytics Dashboard.

Triggering in-app messages

When creating in-app campaigns in the Localytics Dashboard, you decide under which conditions the in-app creative should display. You can trigger in-app messages to display at session start. You can also trigger in-app messages to display when a particular event is tagged using the event name alone or using the event name combined with attribute conditions.

Sometimes, you just want to display an in-app message, but there is not an existing event tag of which to take advantage, and you don't want to create an additional event datapoint solely to display a message. In this situation, Localytics marketing triggers allow you to trigger in-app messages off of specific user behaviors that you do not have tagged as an event in the Localytics dashboard.

There are a variety of scenarios where these triggers might be relevant:

  • You have a lot of Summary Events tagged, and Summary Events do not allow for granular behavioral triggering of messages.
  • You do not want to pass a particular behavior as an Event to the Localytics dashboard because the behavior is so frequent that an Event tag would incur high data point usage.

Instrumenting marketing triggers

Instrumenting Marketing Triggers in the app’s code base is simple. It’s essentially the same as tagging an Event, but rather than using [Localytics.tagEvent_Name] you will use the following

Objective-C Swift

[Localytics triggerInAppMessage:@"Item Purchased"];
Localytics.triggerInAppMessage("Item Purchased")

Marketing triggers with attributes

To create a trigger with additional attribute conditions, use

Objective-C Swift

[Localytics triggerInAppMessage:@"Item Purchased" withAttributes:@{@"Item name" : @"Stickers"}];
Localytics.triggerInAppMessage("Item Purchased", withAttributes: ["Item name" : "Stickers"])

Because there’s no data about that trigger in the Localytics backend, you’ll need to manually send the trigger attribute mappings to Localytics via a reported Event instead, at least one time. Essentially, you must fire an actual Event that reaches Localytics server once. You can do this by:

For one session tied to your app key - for example, just in your dev build but tagged with your production key temporarily - switch that triggerInAppMessage call to a standard tagEvent call instead, and run the app through that code to actually send the event to Localytics.

This will populate the event name and attributes in the autocomplete dialog within 10 minutes. Then you can switch the tag back from tagEvent to triggerInAppMessage. From there, you will be able to target on the trigger & attributes as if it were a normal event in the dashboard.

Selecting marketing triggers in the dashboard

Once the marketing trigger has been instrumented in the app’s code base, you can use these to trigger in-app messages in the Localytics dashboard. When you get to the Scheduling page of the campaign setup process, you will chose the “Event” trigger. For these marketing triggers, you will need to type in the name of the marketing trigger in the drop down (as seen below) - it will not automatically populate as the dashboard events do.

Customizing in-app messages

Dismiss button image

By default, there is a dismiss button in the shape of an "X" on the top-left of the in-app message dialog. Use the methods below to set the dismiss button to a different image or to change the position of the button to the top-right. If you change the image, the Localytics SDK creates a copy of the referenced image based on the maximum allowed image size, so your app doesn't have to keep the image after the call.

To set the image by name, use

Objective-C Swift

[Localytics setInAppMessageDismissButtonImageWithName:@"custom_dismiss_button"];
Localytics.setInAppMessageDismissButtonImageWithName("custom_dismiss_button")

To set the image by image, use

Objective-C Swift

[Localytics setInAppMessageDismissButtonImage:[UIImage imageNamed:@"custom_dismiss_button"]];
Localytics.setInAppMessageDismissButtonImage(UIImage(named: "custom_dismiss_button"))

If you want to clear the image and switch back to the default, pass a nil to this method as below.

Objective-C Swift

[Localytics setInAppMessageDismissButtonImage:nil];
Localytics.setInAppMessageDismissButtonImage(nil)

Dismiss button location

To set the dismiss button location to the left, use

Objective-C Swift

[Localytics setInAppMessageDismissButtonLocation:LLInAppMessageDismissButtonLocationLeft];
Localytics.setInAppMessageDismissButtonLocation(.left)

To set the dismiss button location to the right, use

Objective-C Swift

[Localytics setInAppMessageDismissButtonLocation:LLInAppMessageDismissButtonLocationRight];
Localytics.setInAppMessageDismissButtonLocation(.right)

Customize In-Apps using HTML (SDK 5.4+)

As of SDK 5.4 you can modify some native elements of In App messages by modifying the index.html file of your creative. To do so, attach a data-localytics attribute to the meta name="viewport" element. The attributes should be a String containing a number of key values seperated by commas. An example might look as follows:

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" data-localytics="notch_fullscreen=true, close_button_visibility=visible, close_button_position=right, offset=50, aspect_ratio=1.5, background_alpha=0.8 video_conversion_percentage=80" />

The list of available modifications is as follows:

  • notch_fullscreen: Modify if the In App will render across the entirety of the screen (outside of the safe area) on devices with a notch such as the iPhone X series. If set to true, this will render into the space surrounding the notch.
  • close_button_visibility: Modify the visibility of the dismiss button. Valid values are hidden and visible. This option should only be used if the creative provides it's own close button.
  • close_button_position: Modify the position of the dismiss button. Valid values are right and left.
  • banner_offset: Only relevant to banner In App campaigns, this key defines an offset in pixels from the top or bottom of the screen. Valid values are any positive integer value.
  • aspect_ratio: Only relevant to center In App campaigns, this key modifies the aspect ratio of the native window. Valid values are any float, although we suggest keeping the range within 1.0 and 3.0.
  • background_alpha: Only relevant to fullscreen and center In App campaigns, this key modifies the transparency of the native window. Valid values are any float between 0.0 (transparent) and 1.0 (opaque).
  • video_conversion_percentage: Only available in SDK 5.7 and up - If a video is present in the campaign, this sets a percentage of the video, that is watched will trigger the Localytics SDK to tag a Localytics Video Played event.

Customize In-Apps with a Callback

Using a messaging callback, as described in the advanced section, can allow for customization of individual in-app creatives right before they are shown to the user. The list of configurable properties are available on an object passed through the callback, the LLInAppConfiguration. Modification of these values will result in different displays for an individual creative. The list is as follows:

  • Aspect Ratio: The value passed in should be a float value representing a ratio of height to width. This property is only relevant for center in-app creatives.
  • Offset: The value passed in should be a positive pixel offset from the top or bottom of the screen. This property is only relevant for top and bottom banner in-app creatives.
  • Background Alpha: The value passed in should be a float value between 0.0 (transparent) and 1.0 (opaque).
  • Dismiss Button Image: The value passed in should be a UIImage. A helper method (LLInAppConfiguration setDismissButtonImageWithName:]) also exists to add an image by name. This value will override any values set globally with [Localytics setInAppMessageDismissButtonImage:]
  • Dismiss Button Location: The value passed in should be either LLInAppMessageDismissButtonLocationLeft for in-app creatives with a dismiss button on the left, or LLInAppMessageDismissButtonLocationRight for in-app creatives with a dismiss button on the right. This value will override any values set globally with [Localytics setInAppMessageDismissButtonLocation:]
  • Dismiss Button Hidden: The value passed in should be a boolean corresponding to the visible state of the button. If YES or true is passed in then the dismiss button will be hidden for this in app creative.
  • Home Screen Indicator Visibility (SDK 5.4+): The value passed in should be a boolean corresponding to the visible state of the home screen indicator. If YES or true is passed in then the home screen indicator button will be hidden for this in app creative.
  • Fullscreen preference (SDK 5.4+): The value passed in should be a boolean corresponding to the preference for rendering outside of the safe area on devices with notches (such as the iPhone X series). Setting this value to YES or true will result in the In App extending outside of the safe area.

A simple example of using the callbacks to modify an in-app creative may look as follows:

Objective-C Swift

- (nonnull LLInAppConfiguration *)localyticsWillDisplayInAppMessage:(nonnull LLInAppCampaign *)campaign withConfiguration:(nonnull LLInAppConfiguration *)configuration {
  if ([configuration isCenterCampaign]) {
    configuration.aspectRatio = 1.2;
  } else if ([configuration isTopBannerCampaign] || [configuration isBottomBannerCampaign]) {
    configuration.offset = 50;
  }
  configuration.backgroundAlpha = 0;
  configuration.dismissButtonImage = [UIImage imageNamed:@"custom_dismiss_button"];
  configuration.dismissButtonLocation = LLInAppMessageDismissButtonLocationRight;
  configuration.dismissButtonHidden = YES;
  configuration.autoHideHomeScreenIndicator = YES;
  configuration.notchFullScreen = YES;

  return configuration;
}
func localyticsWillDisplay(inAppMessage campaign: LLInAppCampaign, with configuration: LLInAppConfiguration) -> LLInAppConfiguration {
    if configuration.isCenterCampaign() {
        configuration.aspectRatio = 1.2
    } else if configuration.isTopBannerCampaign() || configuration.isBottomBannerCampaign() {
        configuration.offset = 50
    }
    configuration.backgroundAlpha = 0
    configuration.dismissButtonImage = UIImage(named:"custom_dismiss_button")
    configuration.dismissButtonLocation = .right
    configuration.dismissButtonHidden = true
    configuration.autoHideHomeScreenIndicator = true;
    configuration.notchFullScreen = true;

    return configuration
}

Custom Creative Javascript API

The Localytics SDK adds a localytics Javascript function to the in-app HTML that provides access to several native SDK methods and properties and handles URL navigation and in-app dismissal.

The localytics Javascript function is only added to the in-app after the web view has finished loading, and initial rendering has completed. As a result, if you are trying to render content based on Localytics data (such as a custom dimension), we suggest calling Localytics asynchronously and including some type of loading indicator.

The sections below cover the details of the in-app Javascript API. It is crucial to understand these APIs when designing and uploading your own custom creatives. Note: When using the in-app message builder, calls to these APIs will be handled automatically.

Properties Accessors

  • localytics.campaign (Only available on SDK 4.3 and later): Returns a Javascript object containing information about the campaign that triggered this In-App message. The javascript object contains the campaign name as it would appear on the dashboard (name), the campaign ID (campaignId), the name of the event that triggered the In-App (eventName), and the attribute keys and values tagged on the event or trigger that launched the in-app message (eventAttributes).

    var campaign = localytics.campaign; // {"name": "App Upgrade Campaign", "campaignId": "449859", "eventName": "App Launch", "eventAttributes": {"isFirstSession": "NO"}};
    
  • localytics.identifiers: Returns a Javascript object containing a user's identifying data: customer_id, first_name, last_name, full_name, and email. Note: These values will only be populated if you have set them for the user directly via the SDK.

    var identifiers = localytics.identifiers; // {"customer_id": "3neRKTxbNWYKM4NJ", "first_name": "John", "last_name": "Smith", "full_name": "Sir John Smith, III", "email": "sir.john@smith.com"};
    
  • localytics.customDimensions: Returns a Javascript object containing the current session's custom dimensions. The keys of the object will be "c0", "c1", etc. for dimensions that have been set.

    var dimensions = localytics.customDimensions; // {"c0": "Paid", "c1": "Logged In"};
    
  • localytics.attributes: Returns a Javascript object containing the attribute keys and values tagged on the event or trigger that launched the in-app message.

    var eventAttributes = localytics.attributes; // {"Team Name": "Celtics", "City": "Boston"};
    
  • localytics.libraryVersion: Returns the Localytics SDK version as a string.

    var sdkVersion = localytics.libraryVersion; // "iOSa_4.3.0"
    
  • localytics.locationAuthorizationStatus (Only available in SDK v5.2 or later): Returns the device's current location permission authorization status. Possible values are represented by the enum CLAuthorizationStatus and include 0 (Not Determined), 1 (Restricted), 2 (Denied), 3 (Always Authorized), 4 (Authorized When in Use). For more information on these values, please refer to Apple's documentation.

    var authStatus = localytics.locationAuthorizationStatus; // 0
    
  • localytics.notificationAuthorizationStatus (Only available in SDK v5.3 or later): Returns the device's current notification permission authorization status. Possible values are represented by the enum UNAuthorizationStatus and include 0 (Not Determined), 1 (Denied), and 2 (Authorized). For more information on these values, please refer to Apple's documentation.

    var authStatus = localytics.notificationAuthorizationStatus; // 0
    

Methods

  • localytics.tagClickEvent(action) (Only available on SDK 4.3 and later): Tags an in-app clickthrough (also kown as a conversion) with an optional action attribute. If the action attribute is omitted, the default of click will be used. This method can only be called once per in-app. The first time this method is called an event will be recorded, and any subsequent calls will be ignored.

    localytics.tagClickEvent("Share");
    
  • localytics.tagEvent(event, attributes, customerValueIncrease): Tags an event with optional attributes and an optional custom value increase.

    function submitNPS(ratingValue) {
      var attributes = {"Raw Rating": ratingValue};
      if (ratingValue >= 9) {
        attributes["Rating"] = "Promoter";
      } else if (ratingValue <= 6) {
        attributes["Rating"] = "Detractor";
      } else {
        attributes["Rating"] = "Neutral";
      }
      localytics.tagEvent("In-App Rating Result", attributes);
    }
    
  • localytics.setCustomDimension(index, value): Sets a custom dimension value for a particular index.

    localytics.setCustomDimension(0, "Trial");
    
  • localytics.close(): Closes the in-app. If an in-app message viewed event hasn't been tagged (i.e. ampView), an event with ampAction equal to "X" will be tagged.

    function formSubmit() {
      localytics.tagEvent("Form Submit", {"Email": "john@smith.com"});
      localytics.close();
    }
    
  • localytics.setProfileAttribute(name, value, scope) (Only available on SDK 4.3 and later): Sets a profile attribute with an optional scope ("app" or "org").

    localytics.setProfileAttribute("Favorite Team", "Red Sox", "app");
    
  • localytics.deleteProfileAttribute(name, scope) (Only available on SDK 4.3 and later): Delete a profile attribute with an optional scope ("app" or "org").

    localytics.deleteProfileAttribute("Favorite Team", "app");
    
  • localytics.addProfileAttributesToSet(name, values, scope) (Only available on SDK 4.3 and later): Add profile attributes with an optional scope ("app" or "org").

    localytics.addProfileAttributesToSet("Favorite Team", ["Red Sox", "Celtics"], "org");
    
  • localytics.removeProfileAttributesFromSet(name, values, scope) (Only available on SDK 4.3 and later): Remove profile attributes with an optional scope ("app" or "org").

    localytics.removeProfileAttributesFromSet("Favorite Team", ["Red Sox", "Celtics"], "org");
    
  • localytics.incrementProfileAttribute(name, value, scope) (Only available on SDK 4.3 and later): Increment a profile attribute with an optional scope ("app" or "org").

    localytics.incrementProfileAttribute("Age", 1, "app");
    
  • localytics.decrementProfileAttribute(name, value, scope) (Only available on SDK 4.3 and later): Decrement a profile attribute with an optional scope ("app" or "org").

    localytics.decrementProfileAttribute("Days Until Graduation", 3, "org");
    
  • localytics.setCustomerFirstName(name, value, scope) (Only available on SDK 4.3 and later): Set the user's first name.

    localytics.setCustomerFirstName("John");
    
  • localytics.setCustomerLastName(name, value, scope) (Only available on SDK 4.3 and later): Set the user's last name.

    localytics.setCustomerLastName("Smith");
        
    
  • localytics.setCustomerFullName(name, value, scope) (Only available on SDK 4.3 and later): Set the user's full name.

    localytics.setCustomerFullName("Sir John Smith, III");
        
    
  • localytics.setCustomerEmail(name, value, scope) (Only available on SDK 4.3 and later): Set the user's email.

    localytics.setCustomerEmail("sir.john@smith.com");
        
    
  • localytics.setOptedOut(optedOut) (Only available on SDK 5.2 and later): Opt the user into or out of data collection. See the advanced section for more details on the implications of this call.

    localytics.setOptedOut([true/false]);
        
    
  • localytics.setPrivacyOptedOut(optedOut) (Only available on SDK 5.2 and later): Opt the user into or out of data collection. See the advanced section for more details on the implications of this call.

    localytics.setPrivacyOptedOut([true/false]);
        
    
  • localytics.promptForNotificationPermissions(action) (Only available on SDK 5.2 and later): Prompt the user for notification permissions using the OS specific permission prompt. This method can only be called once per in-app. The first time this method is called it will tag a conversion event with the action parameter, prompt the user for notification permissions, and then dismiss the In-App. If the action attribute is omitted, the default of click will be used as the conversion event.

    localytics.promptForLocationAlwaysPermissions("Notification Permission Accepted");
              
    

    This method will not prompt the user for notification permissions if they have already accepted or denied the permission.

  • localytics.promptForLocationWhenInUsePermissions(action) (Only available on SDK 5.2 and later): Prompt the user for location permissions when the app is in use using the OS specific permission prompt. This method can only be called once per in-app. The first time this method is called it will tag a conversion event with the action parameter, prompt the user for location permissions, and then dismiss the In-App. If the action attribute is omitted, the default of click will be used as the conversion event.

    localytics.promptForLocationWhenInUsePermissions("When In Use Accepted");
              
    

    This method will not prompt the user for Location permissions if any of the following are true:

    • If the info.plist is missing the value for the key NSLocationWhenInUseUsageDescription
    • If Location permissions have already been granted.
    • If Location permissions have already been denied.
  • localytics.promptForLocationAlwaysPermissions(action) (Only available on SDK 5.2 and later): Prompt the user for location permissions using the OS specific permission prompt. This method can only be called once per in-app. The first time this method is called it will tag a conversion event with the action parameter, prompt the user for location permissions, and then dismiss the In-App. If the action attribute is omitted, the default of click will be used as the conversion event.

    localytics.promptForLocationAlwaysPermissions("Always Accepted");
              
    

    This method will not prompt the user for Location permissions if any of the following are true:

    • If the info.plist is missing the values for the keys NSLocationAlwaysAndWhenInUseUsageDescription or NSLocationAlwaysUsageDescription.
    • If Location permissions have already been granted for any state other than when in use.
    • If Location permissions have already been denied.
  • localytics.deeplinkToSettings(action) (Only available on SDK 5.3 and later): Trigger a deeplink to the phone's settings screen. This method can only be called once per in-app. The first time this method is called it will tag a conversion event with the action parameter and then deeplink to the notification specific settings page. If the action attribute is omitted, the default of click will be used as the conversion event.

    localytics.deeplinkToSettings("Deeplink");
              
    

URL and Deep Link Navigation

Supported Deep Link Identifiers

Localytics supports a number of schemes for deeplinking. These schemes each come with a set of query parameters that can be appended to the deeplink url to specify certain behaviors. The available query parameters are as follows:

  • ampAction - This parameter will become the value of the Action attribute on the In App viewed event ampView. If nothing is specified, then a default of Click will be used.
  • ampExternalOpen - This parameter indicates to Localytics if it should open the deeplink in the In App (if possible) or externally.
The above mentioned parameters will be removed from the deeplink after they have been consumed by Localytics.

To be sure your deep link URL is supported by Localytics, make sure it corresponds to one of the following formats and is properly URL encoded. The examples below show the proper usage of Localytics query parameters.

Deeplink Scheme Required Query Params Destination
https ampAction, ampExternalOpen The phone's browser if ampExternalOpen=true or inside the In App if ampExternalOpen=false
file ampAction This will open a file from the root of the creative directory inside the in-app window (ampAction is optional here).
myApp ampAction The app associated with the deeplink scheme
*mailto ampAction The preferred mail app on the device
*tel ampAction The device's phone

Some examples of each:

  • https://example.com?ampAction="MY_CUSTOM_ACTION"&ampExternalOpen=true
  • file://index.html?ampAction="MY_CUSTOM_ACTION"
  • myApp://deep_link_app_content?ampAction="MY_CUSTOM_ACTION"
  • mailto:person@localytics.com?ampAction="MY_CUSTOM_ACTION"
  • tel:838-838-8383?ampAction="MY_CUSTOM_ACTION"
*Both "mailto" and "tel" require SDK version 4.3 or later for appended tracking to be handled correctly.

Universal links require SDK 5.0 as well as some additional setup defined in the advanced section.

Tagging Click Events

All displayed in-app messages tag a single viewed event (i.e. ampView) with an action attribute (i.e. ampAction). Each viewed event is described as an "impression" on the campaign performance page in the Localytics Dashboard. To record a campaign clickthrough (also known as a conversion) when a call-to-action (CTA) is pressed, append the ampAction=click key-value query string to the end of your web or deep link URL. Note: This query paramater is added automatically when entering a URL or deep link into the CTA field in the campaign editor in the Localytics Dashboard.

// Opening a web URL
function openHomePage() {
  window.open("https://www.localytics.com?ampAction=click&ampExternalOpen=true");
  localytics.close();
}

// Opening a deep link URL
function openFavorites() {
  window.open("myApp://favorites?ampAction=click");
  localytics.close();
}

If you have multiple CTAs within an in-app creative, you can use different ampAction values to distinguish each clickthrough. Any ampAction not equal to "X" or "close" will be considered a clickthrough. By using different ampAction values, you will be able to see which CTA the user clicks by navigating to the Events page, clicking "Localytics In-App Displayed", and clicking the "Action" attribute (instead of all CTAs being grouped under a single "click").

// Opening a deep link to the favorites page
function openFavorites() {
  window.open("myApp://favorites?ampAction=favorites");
  localytics.close();
}

// Opening a deep link to the share page
function openShare() {
  window.open("myApp://share?ampAction=share");
  localytics.close();
}

By default, when an in-app is dismissed (by pressing the "X" button or calling localytics.close()) and a viewed event has not yet been tagged, an ampView event is automatically tagged with an ampAction equal to "X".

Opening URLs

Use the Javascript window.open(url) function when opening HTTP and HTTPS URLs and other HTML files contained within the ZIP. URLs can be opened either in the phone's default browser (e.g. Chrome or Safari) or directly within the in-app message view. To control this behavior, append the ampExternalOpen parameter to the URL.

// Opening a web URL in the phone's default browser
function openHomepage() {
  window.open("https://www.localytics.com?ampAction=click&ampExternalOpen=true");
  localytics.close();
}

// Opening a web URL within the in-app message view
function openBlogInsideInApp() {
  window.open("https://info.localytics.com/blog?ampAction=click&ampExternalOpen=false");
}

// Opening another HTML file contained in the ZIP within the in-app message view
function goToFeedback() {
  window.open("feedback.html?ampAction=feedback&ampExternalOpen=false");
}

Push messaging

Push messaging allows you to keep your users up-to-date and reengage them with your app after periods of inactivity.

Before continuing, please be sure that you have completed all of the steps in Getting Started. If you already support push notifications in your app, please skip to the section below about uploading your push certificate. Note that you may still need to enable background modes.

1. Enable push and background modes capabilities

  1. Enter Project Navigator view and click on your project icon.
  2. Select your target from the expanded sidebar or from the dropdown menu next to General then select the Capabilities tab.
  3. If Push Notifications isn't enabled, click the switch to add the "Push Notifications" entitlement to your App ID. If you are using Xcode 8, ensure that an APP-NAME.entitlements file has been added to your project.
  4. If Background Modes isn't enabled, click the switch and then check the Remote notifications box. Localytics uses background modes to track when push messages are received on a user's device before the message is ever opened.
screenshot of remote notifications BackgroundModes in Info.plist

2. Register for a push token

Add the following code to your app delegate's didFinishLaunchingWithOptions: to request a push token from Apple Push Notification Services (APNS). This will not prompt the user for notification display permissions.

Objective-C Swift

[application registerForRemoteNotifications];
application.registerForRemoteNotifications()

3. Register for notifications

On iOS 12+, you have the option of provisionally authorizing users to receive push notifications, even if they haven’t yet opted in to receive notifications. These provisional notifications are “delivered quietly” directly to the device Notification Center. We strongly encourage you to enable provisional authorization, as it can dramatically increase the number of users who can receive push notifications.

Provisional authorization requires that you use the UserNotifications framework. First you must import it:

Objective-C Swift

@import UserNotifications;
import UserNotifications

Then, request provisional authorization after the Localytics initialization code in didFinishLaunchingWithOptions: in your AppDelegate:

Objective-C Swift

if (NSClassFromString(@"UNUserNotificationCenter") && @available(iOS 12.0, *)) {
  UNAuthorizationOptions options = UNAuthorizationOptionProvisional;
  [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:options
            completionHandler:^(BOOL granted, NSError * _Nullable error) {
                [Localytics didRequestUserNotificationAuthorizationWithOptions:options
                            granted:granted];
  }];
}
if #available(iOS 12.0, *), objc_getClass("UNUserNotificationCenter") != nil {
    let options: UNAuthorizationOptions = [.provisional]
    UNUserNotificationCenter.current().requestAuthorization(options: options) { (granted, error) in
        Localytics.didRequestUserNotificationAuthorization(withOptions: options.rawValue, granted: granted)
    }
} 

Although Provisional Auth allows you to send notifications to users before they've opted into push, we still strongly recommend that you encourage users to fully opt in. When a user opts in, notifications will get shown on the Lock Screen and as banners, badges will be incremented, and sounds will played when a notification is received. All of this increases the prominence of the notification, increasing the chances that it will be seen by the user.

The most effect way to drive opt ins is through a soft-ask, which we discuss here. However, if you would prefer to trigger the system prompt for push authorization some other way, you can call the following:

Objective-C Swift

if (NSClassFromString(@"UNUserNotificationCenter")) {
  UNAuthorizationOptions options = (UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert);
  [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:options
                                                                      completionHandler:^(BOOL granted, NSError * _Nullable error) {
                                                                        [Localytics didRequestUserNotificationAuthorizationWithOptions:options
                                                                                                                               granted:granted];
                                                                      }];
} else {
  UIUserNotificationType types = (UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound);
  UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
  [application registerUserNotificationSettings:settings];
}
if #available(iOS 10.0, *), objc_getClass("UNUserNotificationCenter") != nil {
    let options: UNAuthorizationOptions = [.alert, .badge, .sound]
    UNUserNotificationCenter.current().requestAuthorization(options: options) { (granted, error) in
        Localytics.didRequestUserNotificationAuthorization(withOptions: options.rawValue, granted: granted)
    }
} else {
    let types: UIUserNotificationType = [.alert, .badge, .sound]
    let settings = UIUserNotificationSettings(types: types, categories: nil)
    application.registerUserNotificationSettings(settings)
}
If you are using manual integration, be sure to follow the documentation on handling the notification events.

4. Handle opened remote notifications

This step is only required if you are using the UserNotification framework introduced in iOS 10 and you are setting a UNUserNotificationCenterDelegate. If this doesn't apply to your app, please continue on to creating a univeral push notification client SSL certificate.

When using the UserNotification framework and using a UNUserNotificationCenterDelegate, you need to notify the Localytics SDK when notifications are opened. Add the following to your userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: delegate method:

Objective-C Swift

- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler {
  [Localytics didReceiveNotificationResponseWithUserInfo:response.notification.request.content.userInfo];
  completionHandler();
}
@available(iOS 10.0, *) // if also targeting iOS versions less than 10.0
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    Localytics.didReceiveNotificationResponse(userInfo: response.notification.request.content.userInfo)
    completionHandler()
}

5. Create a universal push notification client SSL certificate

To prepare for push notifications on iOS, you need to create a Universal Push Notification Client SSL Certificate that allows Localytics to connect to the Apple Push Notification service. A universal certificate allows your app to connect to both the development and production environments.

  1. Log in to the Apple Developer console and go to Certificates, Identifiers & Profiles.
  2. Under Certificates, select All and then click the Add button (+) in the upper-right corner.
  3. Under Production, select the "Apple Push Notification service SSL (Sandbox & Production)" checkbox, and click Continue. screenshot of univeral push certificate creation
  4. Choose an App ID from the App ID pop-up menu, and click Continue. Choose the explicit App ID that matches your bundle ID. If your explicit App ID doesn't appear in the list, follow our instructions for creating an explicit App ID and then return to this step.
  5. Follow the instructions on the next webpage to create a certificate request on your Mac, and click Continue.
  6. On the Generate your certificate page, click Choose File and choose the certificate request file you just created (with a .certSigningRequest extension) and then click Continue.
  7. Download the generated certificate to your Mac and then double-click the .cer file to install it in Keychain Access. Finally, click Done.

6. Verify your App ID is push enabled

  1. Log in to the Apple Developer console and go to Certificates, Identifiers & Profiles.
  2. Under Identifiers, select App IDs and then select the explicit App ID that matches the bundle ID.
  3. Verify that a green circle followed by "Enabled" appears in the Push Notifications row and the Distribution column. Because this is a universal certificate, the Development column will remain in the "Configurable" state. This is expected - you don't need to create a development specific certificate because the universal certificate will work within both development and production builds. screenshot of app id that's push enabled

7. Upload your push certificate

In order for Localytics to be able to send pushes on your behalf, you must provide Localytics with the universal push certificate you generated.

  1. On your Mac Launch Keychain Access.
  2. In the Category section, select My Certificates.
  3. Find the certificate you generated and disclose its contents. You’ll see both a certificate and a private key.
  4. Select both the certificate and the key, and choose File > Export Items. screenshot of certificate export from Keychain Access
  5. Save the certificate in the Personal Information Exchange (.p12) format. You can optionally enter a password to protect the item.
  6. Login to the Localytics Dashboard and navigate to Settings > Apps.
  7. Find your app key app in the list and click the gear icon.
  8. Click Add Certs and then click Upload Certificate.
  9. Browse to the location of the .p12 file you created and select it.
  10. Select Development from the Push Mode dropdown menu.
  11. Enter your push certificate password if you previously added one. Leave the Push Password field blank if your push certifcate doesn't have a password.
  12. Click Save.

8. Generate a development build

A development build allows you to most easily test your push integration. You cannot test push in the iOS Simulator, so you must physically connect a device and target it for a build.

  1. Connect a physical device to your computer using a USB cable.
  2. In Xcode, select the device you just connected as the destination for new builds. The destination dropdown is in the toolbar at the top to the right of the run and stop buttons.
  3. Navigate to the Product menu and click Run. You may need to unlock your device.
  4. A development build of your app will appear on your connected device. If you have logging enabled at this point, you should see messages indicating that the Localytics SDK obtained your push token and should see the push token present in the dpush field of the next upload body.

9. Test push integration

If you have integrated SDK version 4.1 or later, use the Rapid Push Verification page to confirm that you have integrated push correctly. Be sure to select the correct app in the dropdown at the top of the page.

screenshot of Rapid Push Verification page

If you experience issues testing your push integration, go through the following steps to troubleshoot your integration:

  1. Make sure your SDK version is 4.1 or later.
  2. Ensure your app has been integrated for Localytics push notifications. Check the "Register for notifications" section in our guide.
  3. Make sure you have generated a development build for your app. See "Generate a development build" to physically connect a device and target it for a build.
  4. Confirm that you are collecting push tokens every session. Be sure to be calling a request to collect tokens early (preferably on app launch) to ensure collection of fresh tokens. See "Register for a push token" for instructions. For manual integrations, call setPushToken in your App Delegate when tokens are collected to send the token to Localytics (see Step 6 in our Manual Integration guide).
  5. If your app is not opening after scanning the QR code on the Rapid Push Verification Page, try opening the link in Safari to see if your app launches. If not, make sure your deep link scheme is set up correctly by going back to "Configure test mode. If still having issues and you have manually integrated, see Step 5 under Manual Integration.
  6. If you opened the push message successfully but no Push Opened event was reported, check that you are properly integrated to "Handle opened remote notifications" in Step 4 (for auto and manual integrations). If still having issues and you have manually integrated, check you have properly integrated Step 8 of Manual Integration.

10. Add support for rich media push attachments (optional)

Rich Push on iOS is purely optional but requires iOS 10, Localytics SDK v4.1 or higher, and the new UserNotifications framework if you wish to use this feature. See our migration guide for details on the prerequisites.

To integrate Rich Push, you'll need to create a new Notification Service Extension:

  1. Open up Xcode and navigate to File > New > Target. The following dialog will pop up:
  2. Select the Notification Service Extension option, click "Next", fill out the name, and click "Finish". Xcode will now create a new top level folder in your project with the name you provided, containing a NotificationServiceExtension class.
  3. Include the below code snippet in the didReceiveNotificationRequest:withContentHandler: method of the extension.

    Objective-C Swift

    - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
      self.contentHandler = contentHandler;
      self.bestAttemptContent = [request.content mutableCopy];
    
        // Modify the notification content here...
        NSString *imageURL = self.bestAttemptContent.userInfo[@"ll_attachment_url"];
        NSString *imageType = self.bestAttemptContent.userInfo[@"ll_attachment_type"];
        if (!imageURL || !imageType) {
            //handle non localytics rich push
            self.contentHandler(self.bestAttemptContent);
            return;
        }
        NSURL *url = [NSURL URLWithString:imageURL];
        [[[NSURLSession sharedSession] downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable tempFile, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (error) {
                NSLog(@"Failed to retrieve attachment with error: %@", [error localizedDescription]);
                self.contentHandler(self.bestAttemptContent);
                return;
            }
    
            NSArray *cache = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
            NSString *cachesFolder = cache[0];
    
            NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString];
            NSString *fileName = [[guid stringByAppendingString:@"localytics-rich-push-attachment"] stringByAppendingPathExtension:imageType];
            NSString *cacheFile = [cachesFolder stringByAppendingPathComponent:fileName];
            NSURL *attachmentURL = [NSURL fileURLWithPath:cacheFile isDirectory:NO];
            NSError *err = nil;
            [[NSFileManager defaultManager] moveItemAtURL:tempFile toURL:attachmentURL error:&err];
            if (err) {
                NSLog(@"Failed to save attachment on disk with error: %@", [err localizedDescription]);
                self.contentHandler(self.bestAttemptContent);
                return;
            }
    
            UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"" URL:attachmentURL options:nil error:&err];
            if (attachment) {
                self.bestAttemptContent.attachments = @[attachment];
            } else {
                NSLog(@"Failed to create attachment with error: %@", [err localizedDescription]);
            }
            self.contentHandler(self.bestAttemptContent);
        }] resume];
    }
      
    
    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
      self.contentHandler = contentHandler
      self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
    
      if let bestAttemptContent =  bestAttemptContent {
          if let attachmentURL = bestAttemptContent.userInfo["ll_attachment_url"] as? String, let attachmentType = bestAttemptContent.userInfo["ll_attachment_type"] as? String {
              guard let url = URL(string: attachmentURL) else {
                  print("Failed to present attachment due to an invalid url: %@", attachmentURL)
                  contentHandler(bestAttemptContent)
                  return
              }
    
              let task = URLSession.shared.downloadTask(with: url) { (tempFile, response, error) in
                  if let error = error {
                      print("Failed to retrieve attachment with error: %@", error.localizedDescription)
                      contentHandler(bestAttemptContent)
                      return
                  }
                  guard let tempFile = tempFile else {
                      print("Failed to store contents of attachment on disk")
                      contentHandler(bestAttemptContent)
                      return
                  }
    
                  let cache = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
                  let cachesFolder = cache[0]
    
                  let guid = ProcessInfo.processInfo.globallyUniqueString
                  let cacheFile = "\(cachesFolder)/\(guid)-localytics-rich-push-attachment.\(attachmentType)"
                  let attachmentURL = URL(fileURLWithPath: cacheFile)
    
                  do {
                      try FileManager.default.moveItem(at: tempFile, to: attachmentURL)
    
                      let attachment = try UNNotificationAttachment(identifier: "localytics-rich-push-attachment", url: attachmentURL, options: nil)
                      bestAttemptContent.attachments = [attachment]
                  } catch _ {
                      print("Failed to create attachment for push notification")
                  }
    
                  contentHandler(bestAttemptContent)
              }
              task.resume()
          } else {
              //handle non localytics rich push
              contentHandler(bestAttemptContent)
          }
      }
    }
    
      
    

11. Add support for categories and actions (optional)

Categories and actions on iOS are purely optional but require Localytics SDK v4.4 or higher for proper reporting functionality.

Steps to integrate categories and actions:

  1. Register your desired categories and actions. These are generally configured within your App Delegate. For apps targeting iOS 10 and using the UserNotifications framework:

    Objective-C Swift

    UNNotificationAction *like = [UNNotificationAction actionWithIdentifier:@"like" title:@"Like" options:UNNotificationActionOptionForeground];
    UNNotificationAction *share = [UNNotificationAction actionWithIdentifier:@"share" title:@"Share" options:UNNotificationActionOptionForeground];
    UNNotificationCategory *social = [UNNotificationCategory categoryWithIdentifier:@"social" actions:@[like, share] intentIdentifiers:@[] options:UNNotificationCategoryOptionNone];
    
    NSSet *categories = [NSSet setWithArray:@[social]];
    [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:categories];
          
    
    let likeAction = UNNotificationAction(identifier: "like", title: "Like", options: .foreground)
    let shareAction = UNNotificationAction(identifier: "share", title: "Share", options: .foreground)
    let socialCategory = UNNotificationCategory(identifier: "social", actions: [likeAction, shareAction], intentIdentifiers: [], options: [])
    
    var categories = Set<UNNotificationCategory>()
    categories.insert(socialCategory)
    UNUserNotificationCenter.current().setNotificationCategories(categories)
          
    

    For apps targeting iOS 9 use the following:

    Objective-C Swift

    UIMutableUserNotificationAction *like = [[UIMutableUserNotificationAction alloc] init];
    like.title = @"Like";
    like.identifier = @"like";
    like.activationMode = UIUserNotificationActivationModeForeground;
    
    UIMutableUserNotificationAction *share = [[UIMutableUserNotificationAction alloc] init];
    share.title = @"Share";
    share.identifier = @"share";
    share.activationMode = UIUserNotificationActivationModeForeground;
    
    UIMutableUserNotificationCategory *social = [[UIMutableUserNotificationCategory alloc] init];
    social.identifier = @"social";
    [social setActions:@[like, share] forContext:UIUserNotificationActionContextMinimal];
    
    NSSet *categories = [NSSet setWithArray:@[social]];
    [application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:types categories:categories]];
          
    
    let like = UIMutableUserNotificationAction()
    like.title = "Like"
    like.identifier = "like"
    like.activationMode = .foreground
    
    let share = UIMutableUserNotificationAction()
    share.title = "Share"
    share.identifier = "share"
    share.activationMode = .foreground
    
    let social = UIMutableUserNotificationCategory()
    social.identifier = "social"
    social.setActions([like, share], for:.minimal)
    
    application.registerUserNotificationSettings(UIUserNotificationSettings(types:.alert, categories:[social]))
    
  2. Handle the actions when the respective action button is pressed and pass the action identifier to Localytics.

    Objective-C Swift

    -(void) userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse: UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
    
      [Localytics didReceiveNotificationResponse:response.notification.request.content.userInfo andActionIdentifier: response.actionIdentifier];
    
      if ([@"like" isEqualToString:response.actionIdentifier]) {
        //handle behavior for like button
      } else if ([@"share" isEqualToString:response.actionIdentifier]) {
        //handle behavior for share button
      } else if ([UNNotificationDefaultActionIdentifier isEqualToString:response.actionIdentifier]) {
        //handle open behavior
      } else if([UNNotificationDismissActionIdentifier isEqualToString:response.actionIdentifier]) {
        //handle dismiss behavior
      }
      if (completionHandler) {
        completionHandler();
      }
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
      Localytics.didReceiveNotificationResponse(userInfo: response.notification.request.content.userInfo, andActionIdentifier: response.actionIdentifier)
    
      switch response.actionIdentifier {
        case "like": break // handle like button
        case "share": break // handle share button
        case UNNotificationDefaultActionIdentifier: break // handle default open
        case UNNotificationDismissActionIdentifier: break // handle dismiss
        default: break // handle other cases
      }
    
      completionHandler()
    }
    
  3. If you are using manual integration and targeting iOS 9 and below, implement methods in your App Delegate to handle the actions when notifications are received.

    Objective-C Swift

    - (void)application:(UIApplication *)application handleActionWithIdentifier:(nullable NSString *)identifier forRemoteNotification:(nonnull NSDictionary *)userInfo completionHandler:(nonnull void (^)())completionHandler {
      [self.localyticsDelegate handleNotification:userInfo withActionIdentifier:identifier];
      if (completionHandler){
        completionHandler(UIBackgroundFetchResultNoData);
      }
    }
    
    - (void)application:(UIApplication *)application handleActionWithIdentifier:(nullable NSString *)identifier forRemoteNotification:(nonnull NSDictionary *)userInfo withResponseInfo:(nonnull NSDictionary *)responseInfo completionHandler:(nonnull void (^)())completionHandler {
      [self.localyticsDelegate handleNotification:userInfo withActionIdentifier:identifier];
      if (completionHandler){
        completionHandler(UIBackgroundFetchResultNoData);
      }
    }
    
    func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, forRemoteNotification userInfo: [AnyHashable : Any], completionHandler: @escaping () -> Void) {
      Localytics.handleNotification(userInfo, withActionIdentifier:identifier)
    }
    
    func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, forRemoteNotification userInfo: [AnyHashable : Any], withResponseInfo responseInfo: [AnyHashable : Any], completionHandler: @escaping () -> Void) {
      Localytics.handleNotification(userInfo, withActionIdentifier:identifier)
    }
    

12. Before you release to the App Store

Before you release your app to the App Store, you will want to test that it can properly receive not only development push messages as above, but also production push messages. We also recommend using a different Localytics app key for the production build of your app.

  1. If your production build uses a different App ID/Bundle ID than your development build, follow the creating a universal certificate and verifying your App ID is push enabled instructions to generate a universal push notification certificate for this App ID.
  2. Upload your universal push certificate to Localytics for your production app key. Follow the uploading your push certificate instructions but select Production from the Push Mode dropdown menu. You can use the same certificate (.p12) if your production build uses the same App ID/Bundle ID as your development build.
  3. Create a distribution provisioning profile. Follow the instructions for creating Ad Hoc provisioning profiles. Ad Hoc builds are just like App Store builds except that you can only install them on a limited set of predetermined devices. Both types of builds will use Apple's Production Push environment.
  4. Generate a distribution build of your app and install it on a connected test device. If you have logging enabled at this point, you should see messages indicating that the Localytics SDK obtained your push token and should see the push token present in the push (not dpush) field of the next upload body.
  5. Send yourself a test push message using the same instructions as before.
  6. Complete all of the same steps above except select App Store as the provisioning profile type. If you are using multiple Localytics app keys, e.g. one for development and one for production, make sure to upload your production push certificate to your production app key.

App inbox

App Inbox allows you to deliver personalized content to users through a dedicated inbox inside your app. Create Inbox Campaigns using templated or custom HTML creatives from within the Localytics Dashboard. Inbox messages will display in your users' inbox for a scheduled amount of time.

Before continuing, please be sure that you have completed all of the steps in Getting Started.

To add App Inbox to your app you need to include a list of inbox messages within your app's user interface and then handle displaying the inbox message detail view. The instructions below will walk your through the process.

1. Include a list of inbox messages

You have 2 options for adding a list of inbox messages to your app:

  1. Using LLInboxViewController, which is the recommended and simplest approach.
  2. Getting inbox campaigns from the Localytics class methods and displaying them in your own UIViewController.

Using LLInboxViewController (recommended)

LLInboxViewController can be added to your app using a storyboard or by initializing a new instance programmatically.

To add LLInboxViewController to an existing storyboard:

  1. Drag a new UIViewController object into the storyboard canvas and embed it in a UINavigationController.
  2. Select the new UIViewController, open the Identity inspector, and set the class name to LLInboxViewController.
screenshot of LLInboxViewController in a storyboard

To programmatically add LLInboxViewController, create a new instance and push it onto a UINavigationController stack as follows.

Objective-C Swift

- (void)displayInbox {
    LLInboxViewController *inboxViewController = [[LLInboxViewController alloc] init];
    [self.navigationController pushViewController:inboxViewController
                                         animated:YES];
}
func displayInbox() {
  let inboxViewController = LLInboxViewController()
  self.navigationController?.pushViewController(inboxViewController, animated: true)
}

If you prefer to present LLInboxViewController modally, include it within a UINavigationController as follows.

Objective-C Swift

- (void)displayInbox {
    LLInboxViewController *inboxViewController = [[LLInboxViewController alloc] init];
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:inboxViewController];
    [self presentViewController:navigationController
                       animated:YES
                     completion:nil];
}
func displayInbox() {
  let inboxViewController = LLInboxViewController()
  let navigationController = UINavigationController(rootViewController: inboxViewController)
  present(navigationController, animated: true, completion: nil)
}

Using your own UIViewController

If you prefer to not use LLInboxViewController, you can get inbox data directly via the Localytics class methods and display it in your own UIViewController.

To retrieve cached inbox campaign data, call [Localytics inboxCampaigns] from a background thread as follows.

Objective-C Swift

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSArray<LLInboxCampaign *> *inboxCampaigns = [Localytics inboxCampaigns];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
  let inboxCampaigns = Localytics.inboxCampaigns()
}

To refresh inbox campaign data from the network, call [Localytics refreshInboxCampaigns:] with a completion block as follows.

Objective-C Swift

[Localytics refreshInboxCampaigns:^(NSArray<LLInboxCampaign *> *inboxCampaigns) {
    // refresh UI using inboxCampaigns
}];
Localytics.refreshInboxCampaigns { (inboxCampaigns) in
  // refresh UI using inboxCampaigns
}

2. Display a detail inbox message view

You must use LLInboxDetailViewController to display a detail inbox message view. When using LLInboxViewController the default behavior will automatically mark the inbox message as read and create an LLInboxDetailViewController and push it onto the navigation stack. If you are not using LLInboxViewController, you can get an instance from the Localytics class as follows.

Objective-C Swift

LLInboxCampaign *campaign = /* campaign from [Localytics inboxCampaigns] */;
LLInboxDetailViewController *inboxDetailViewController = [Localytics inboxDetailViewControllerForCampaign:campaign];
let campaign = /* campaign from Localytics.inboxCampaigns() */
let inboxDetailViewController = Localytics.inboxDetailViewController(for: campaign)

3. Customize the appearance and behavior

To match the look and feel of your app, there are several customization options available for App Inbox.

Changing fonts and color

To customize the font and color of the text, details, and time labels and the unread indicator color, set properties on LLInboxViewController as follows.

Objective-C Swift

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"inbox_list"]) {
        LLInboxViewController *inboxViewController = (LLInboxViewController *) segue.destinationViewController;
        inboxViewController.textLabelColor = [UIColor redColor];
        inboxViewController.textLabelFont = [UIFont boldSystemFontOfSize:24.0f];
        inboxViewController.detailTextLabelColor = [UIColor greenColor];
        inboxViewController.detailTextLabelFont = [UIFont boldSystemFontOfSize:16.0f];
        inboxViewController.timeTextLabelColor = [UIColor blueColor];
        inboxViewController.timeTextLabelFont = [UIFont systemFontOfSize:12.0f];
        inboxViewController.unreadIndicatorColor = [UIColor yellowColor];
        inboxViewController.cellBackgroundColor = [UIColor whiteColor];
    }
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "inbox_list" {
        let inboxViewController = segue.destination as! LLInboxViewController
        inboxViewController.textLabelColor = .red
        inboxViewController.textLabelFont = .boldSystemFont(ofSize: 24)
        inboxViewController.detailTextLabelColor = .green
        inboxViewController.detailTextLabelFont = .boldSystemFont(ofSize: 16)
        inboxViewController.timeTextLabelColor = .blue
        inboxViewController.timeTextLabelFont = .boldSystemFont(ofSize: 12)
        inboxViewController.unreadIndicatorColor = .yellow
        inboxViewController.cellBackgroundColor = .white
    }
}

Objective-C Swift

- (void)displayInbox {
    LLInboxViewController *inboxViewController = [[LLInboxViewController alloc] init];
    inboxViewController.textLabelColor = [UIColor redColor];
    inboxViewController.textLabelFont = [UIFont boldSystemFontOfSize:24.0f];
    inboxViewController.detailTextLabelColor = [UIColor greenColor];
    inboxViewController.detailTextLabelFont = [UIFont boldSystemFontOfSize:16.0f];
    inboxViewController.timeTextLabelColor = [UIColor blueColor];
    inboxViewController.timeTextLabelFont = [UIFont systemFontOfSize:12.0f];
    inboxViewController.unreadIndicatorColor = [UIColor yellowColor];
    inboxViewController.cellBackgroundColor = [UIColor whiteColor];
    [self.navigationController pushViewController:inboxViewController
                                         animated:YES];
}
func displayInbox() {
  let inboxViewController = LLInboxViewController()
  inboxViewController.textLabelColor = .red
  inboxViewController.textLabelFont = .boldSystemFont(ofSize: 24)
  inboxViewController.detailTextLabelColor = .green
  inboxViewController.detailTextLabelFont = .boldSystemFont(ofSize: 16)
  inboxViewController.timeTextLabelColor = .blue
  inboxViewController.timeTextLabelFont = .boldSystemFont(ofSize: 12)
  inboxViewController.unreadIndicatorColor = .yellow
  inboxViewController.cellBackgroundColor = .white
  self.navigationController?.pushViewController(inboxViewController, animated: true)
}

Displaying LLInboxDetailViewController modally

By default LLInboxViewController will push a new instance of LLInboxDetailViewController onto the navigation stack when an inbox campaign is selected. To present it modally, subclass LLInboxViewController and override tableView:didSelectRowAtIndexPath: as follows.

Objective-C Swift

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    LLInboxCampaign *campaign = [self campaignForRowAtIndexPath:indexPath];
    if (campaign.hasCreative) {
        LLInboxDetailViewController *detailViewController = [Localytics inboxDetailViewControllerForCampaign:campaign];
        UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:detailViewController];
        [self presentViewController:navigationController
                           animated:YES
                         completion:nil];
    } else {
        [Localytics inboxListItemTapped:campaign];
    }

    [tableView deselectRowAtIndexPath:indexPath animated:YES];

    if (!campaign.isRead) {
        // Set campaign as read and reload to refresh unread indicators
        campaign.read = YES;
        [tableView reloadData];
    }
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let campaign = campaignForRow(at: indexPath)!
    if campaign.hasCreative {
        let detailViewController = Localytics.inboxDetailViewController(for: campaign)
        let navigationController = UINavigationController(rootViewController: detailViewController)
        present(navigationController, animated: true, completion: nil)
    } else {
        Localytics.inboxListItemTapped(campaign: campaign);
    }

    tableView.deselectRow(at: indexPath, animated: true)

    if !campaign.isRead {
        // Set campaign as read and reload to refresh unread indicators
        campaign.isRead = true
        tableView.reloadData()
    }
}

Displaying a loading indicator

To show a UIActivityIndicatorView within LLInboxViewController while campaigns are loading, set the showsActivityIndicatorView property to YES before you display it as follows.

Objective-C Swift

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"inbox_list"]) {
        LLInboxViewController *inboxViewController = (LLInboxViewController *) segue.destinationViewController;
        inboxViewController.showsActivityIndicatorView = YES;
    }
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "inbox_list" {
        let inboxViewController = segue.destination as! LLInboxViewController
        inboxViewController.showsActivityIndicatorView = true
    }
}

Objective-C Swift

- (void)displayInbox {
    LLInboxViewController *inboxViewController = [[LLInboxViewController alloc] init];
    inboxViewController.showsActivityIndicatorView = YES;
    [self.navigationController pushViewController:inboxViewController
                                         animated:YES];
}
func displayInbox() {
  let inboxViewController = LLInboxViewController()
  inboxViewController.showsActivityIndicatorView = true
  self.navigationController?.pushViewController(inboxViewController, animated: true)
}

To be notified when inbox campaigns refreshing begins and ends, subclass LLInboxViewController and implement the LLInboxCampaignsRefreshingDelegate protocol methods.

Objective-C Swift

- (void)localyticsDidBeginRefreshingInboxCampaigns {
    // show a loading indicator
}

- (void)localyticsDidFinishRefreshingInboxCampaigns {
    // hide a loading indicator
}
override func localyticsDidBeginRefreshingInboxCampaigns() {
    // show a loading indicator
}

override func localyticsDidFinishRefreshingInboxCampaigns() {
    // hide a loading indicator
}

Setting an empty campaigns view

By default, LLInboxViewController will display text saying "No Messages" when there are no inbox campaigns. To change this behavior, set the emptyCampaignsView property as follows. All subviews of this UIView should include appropriate Auto Layout constraints because this view's leading edge, top edge, trailing edge, and bottom edge will be constrained to match the main view in LLInboxViewController.

Objective-C Swift

- (UIView *)createEmptyView {
    UIView *emptyView = [UIView new];
    emptyView.translatesAutoresizingMaskIntoConstraints = NO;
    emptyView.backgroundColor = [UIColor whiteColor];

    UIImage *emptyImage = [UIImage imageNamed:@"empty_inbox"];
    UIImageView *emptyImageView = [[UIImageView alloc] initWithImage:emptyImage];
    emptyImageView.translatesAutoresizingMaskIntoConstraints = NO;

    [emptyView addSubview:emptyImageView];
    [emptyView addConstraint:[NSLayoutConstraint constraintWithItem:emptyImageView
                                                          attribute:NSLayoutAttributeCenterX
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:emptyView
                                                          attribute:NSLayoutAttributeCenterX
                                                         multiplier:1.0f
                                                           constant:0.0f]];
    [emptyView addConstraint:[NSLayoutConstraint constraintWithItem:emptyImageView
                                                          attribute:NSLayoutAttributeCenterY
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:emptyView
                                                          attribute:NSLayoutAttributeCenterY
                                                         multiplier:1.0f
                                                           constant:0.0f]];
    return emptyView;
}
func createEmptyView() -> UIView {
    let emptyView = UIView()
    emptyView.translatesAutoresizingMaskIntoConstraints = false
    emptyView.backgroundColor = .white

    let emptyImage = UIImage(named: "empty_inbox")
    let emptyImageView = UIImageView(image: emptyImage)
    emptyImageView.translatesAutoresizingMaskIntoConstraints = false

    emptyView.addSubview(emptyImageView)
    emptyView.addConstraint(NSLayoutConstraint(
        item: emptyImageView,
        attribute: .centerX,
        relatedBy: .equal,
        toItem: emptyView,
        attribute: .centerX,
        multiplier: 1,
        constant: 0))
    emptyView.addConstraint(NSLayoutConstraint(
        item: emptyImageView,
        attribute: .centerY,
        relatedBy: .equal,
        toItem: emptyView,
        attribute: .centerY,
        multiplier: 1,
        constant: 0))

    return emptyView
}

Objective-C Swift

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"inbox_list"]) {
        LLInboxViewController *inboxViewController = (LLInboxViewController *) segue.destinationViewController;
        inboxViewController.emptyCampaignsView = [self createEmptyView];
    }
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "inbox_list" {
        let inboxViewController = segue.destination as! LLInboxViewController
        inboxViewController.emptyCampaignsView = createEmptyView()
    }
}

Objective-C Swift

- (void)displayInbox {
    LLInboxViewController *inboxViewController = [[LLInboxViewController alloc] init];
    inboxViewController.emptyCampaignsView = [self createEmptyView];
    [self.navigationController pushViewController:inboxViewController
                                         animated:YES];
}
func displayInbox() {
  let inboxViewController = LLInboxViewController()
  inboxViewController.emptyCampaignsView = createEmptyView()
  self.navigationController?.pushViewController(inboxViewController, animated: true)
}

Using your own UITableViewCell class

To use your own UITableViewCell, subclass LLInboxViewController, disable automatic thumbnail downloading in viewDidLoad, and override tableView:cellForRowAtIndexPath:indexPath and tableView:heightForRowAtIndexPath: as follows.

Objective-C Swift

- (void)viewDidLoad {
    [super viewDidLoad];

    self.downloadsThumbnails = NO;
    [self.tableView registerClass:[UITableViewCell class]
           forCellReuseIdentifier:@"reuseIdentifier"];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    LLInboxCampaign *campaign = [self campaignForRowAtIndexPath:indexPath];
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"reuseIdentifier"];

    /* create and populate cell with campaign data */

    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 44.0f;
}
override func viewDidLoad() {
  super.viewDidLoad()

  self.downloadsThumbnails = false
  self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "reuseIdentifier")
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  let campaign = campaignForRow(at: indexPath)!
  let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier")

  /* create and populate cell with campaign data */

  return cell!
}

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  return 44
}

Handling detail message errors

In rare cases the message detail view may fail to load. By default, LLInboxDetailViewController will display a gray "X" in the center of the view when this occurs. To provide your own custom error view, set the creativeLoadErrorView property of the LLInboxViewController class as follows. All subviews of this UIView should include appropriate Auto Layout constraints because this view's leading edge, top edge, trailing edge, and bottom edge will be constrained to match the main view in LLInboxDetailViewController.

Objective-C Swift

- (UIView *)createErrorView {
    UIView *errorView = [UIView new];
    errorView.translatesAutoresizingMaskIntoConstraints = NO;
    errorView.backgroundColor = [UIColor blackColor];

    UILabel *errorLabel = [UILabel new];
    errorLabel.translatesAutoresizingMaskIntoConstraints = NO;
    errorLabel.textColor = [UIColor whiteColor];
    errorLabel.font = [UIFont boldSystemFontOfSize:30];
    errorLabel.text = @"Error";

    [errorView addSubview:errorLabel];
    [errorView addConstraint:[NSLayoutConstraint constraintWithItem:errorLabel
                                                          attribute:NSLayoutAttributeCenterX
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:errorView
                                                          attribute:NSLayoutAttributeCenterX
                                                         multiplier:1.0f
                                                           constant:0.0f]];
    [errorView addConstraint:[NSLayoutConstraint constraintWithItem:errorLabel
                                                          attribute:NSLayoutAttributeCenterY
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:errorView
                                                          attribute:NSLayoutAttributeCenterY
                                                         multiplier:1.0f
                                                           constant:0.0f]];
    return errorView;
}
func createErrorView() -> UIView {
    let errorView = UIView()
    errorView.translatesAutoresizingMaskIntoConstraints = false
    errorView.backgroundColor = .black

    let errorLabel = UILabel()
    errorLabel.translatesAutoresizingMaskIntoConstraints = false
    errorLabel.textColor = .white
    errorLabel.font = .boldSystemFont(ofSize: 30)
    errorLabel.text = "Error"

    errorView.addSubview(errorLabel)
    errorView.addConstraint(NSLayoutConstraint(
        item: errorLabel,
        attribute: .centerX,
        relatedBy: .equal,
        toItem: errorView,
        attribute: .centerX,
        multiplier: 1,
        constant: 0))
    errorView.addConstraint(NSLayoutConstraint(
        item: errorLabel,
        attribute: .centerY,
        relatedBy: .equal,
        toItem: errorView,
        attribute: .centerY,
        multiplier: 1,
        constant: 0))

    return errorView
}

Objective-C Swift

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"inbox_list"]) {
        LLInboxViewController *inboxViewController = (LLInboxViewController *) segue.destinationViewController;
        inboxViewController.creativeLoadErrorView = [self createErrorView];
    }
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "inbox_list" {
        let inboxViewController = segue.destination as! LLInboxViewController
        inboxViewController.creativeLoadErrorView = createErrorView()
    }
}

Objective-C Swift

- (void)displayInbox {
    LLInboxViewController *inboxViewController = [[LLInboxViewController alloc] init];
    inboxViewController.creativeLoadErrorView = [self createErrorView];
    [self.navigationController pushViewController:inboxViewController
                                         animated:YES];
}
func displayInbox() {
  let inboxViewController = LLInboxViewController()
  inboxViewController.creativeLoadErrorView = createErrorView()
  self.navigationController?.pushViewController(inboxViewController, animated: true)
}

If you are subclassing LLInboxViewController and overriding tableView:cellForRowAtIndexPath:indexPath, set the creativeLoadErrorView property on LLInboxDetailViewController as follows.

Objective-C Swift

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    LLInboxCampaign *campaign = [self campaignForRowAtIndexPath:indexPath];
    if (campaign.hasCreative) {
        LLInboxDetailViewController *detailViewController = [Localytics inboxDetailViewControllerForCampaign:campaign];
        detailViewController.creativeLoadErrorView = [self createErrorView];
        [self.navigationController pushViewController:detailViewController animated:YES];
    }

    [tableView deselectRowAtIndexPath:indexPath animated:YES];

    if (!campaign.isRead) {
        // Set campaign as read and reload to refresh unread indicators
        campaign.read = YES;
        [tableView reloadData];
    }
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let campaign = campaignForRow(at: indexPath)!
    if campaign.hasCreative {
        let detailViewController = Localytics.inboxDetailViewController(for: campaign)
        detailViewController.creativeLoadErrorView = createErrorView()
        self.navigationController?.pushViewController(detailViewController, animated: true)
    }

    tableView.deselectRow(at: indexPath, animated: true)

    if !campaign.isRead {
        // Set campaign as read and reload to refresh unread indicators
        campaign.isRead = true
        tableView.reloadData()
    }
}

Using Inbox Callbacks

Using a messaging callback, as described in the advanced section, gives you an opportunity to execute code before and after creatives are displayed and dismissed.

Deleting Inbox Campaigns (SDK v5.2+)

Starting in SDK v5.2 Inbox campaigns can be deleted. To delete an Inbox Campaign, you can call either of the following methods:

Objective-C Swift

[Localytics deleteInboxCampaign:campaign];
[((LLInboxCampaign *) campaign) delete];
Localytics.deleteInboxCampaign(campaign)
((LLInboxCampaign *) campaign).delete()

Any deleted inbox campaign will not be considered displayable. As a result they will be excluded from the list of campaigns retrieved by calling displayableInboxCampaigns, inboxCampaigns and refreshInboxCampaigns. Additionally, they will be excluded from the count returned by inboxCampaignsUnreadCount.

If you need the list of deleted Inbox campaigns, you can call [Localytics allInboxCampaigns] or [Localytics refreshAllInboxCampaigns:] and filter for campaigns with the property deleted set to true.

Additionally, if you are using the Localytics provided inbox view, you can add swipe to delete from the list view, as well as a delete button in the navigation bar of the detail view by setting a property:

Objective-C Swift

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"inbox_list"]) {
        LLInboxViewController *inboxViewController = (LLInboxViewController *) segue.destinationViewController;
        inboxViewController.enableDetailViewDelete = YES;
        inboxViewController.enableSwipeDelete = YES;
    }
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "inbox_list" {
        let inboxViewController = segue.destination as! LLInboxViewController
        inboxViewController.enableDetailViewDelete = YES;
        inboxViewController.enableSwipeDelete = YES;
    }
}

If you are rendering your own list view, and want to ensure the delete button is available on the navigation bar of the detail view, you must set the property enableDetailViewDelete to true on the LLInboxDetailViewController:

Objective-C Swift

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    LLInboxCampaign *campaign = self.tableData[indexPath.row];
    if (campaign.hasCreative) {
        LLInboxDetailViewController *controller = [Localytics inboxDetailViewControllerForCampaign:campaign];
        controller.deleteInNavBar = self.enableDetailViewDelete;
        [self.navigationController pushViewController:controller animated:YES];
    } else {
        [Localytics inboxListItemTapped:campaign];
    }
    ...
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
    let campaign = self.tableData[indexPath.row];
    if (campaign.hasCreative) {
        let controller = Localytics.inboxDetailViewControllerForCampaign(campaign);
        controller.deleteInNavBar = self.enableDetailViewDelete;
        self.navigationController.pushViewController(controller animated:true);
    } else {
        Localytics.inboxListItemTapped(campaign);
    }
    ...
}

Places

One of the unique aspects of mobile is that users always have their devices with them. Places lets you take advantage of this by sending users content that's personalized to their current location. With Places, you can send notifications to users the instant they enter or exit a specific location. Additionally, you can analyze data about visits to physical locations, giving you access to insights that have never before been available. Read more about setting up Places geofences.

Before continuing, please be sure that you have completed all of the steps in Getting Started and that you are using SDK version 4.0.1 or later. If you want to manually monitor geofences, follow our custom places integration instructions.

1. Add location description keys to your app's Info.plist

Update your app's Info.plist to include the NSLocationWhenInUseUsageDescription and the NSLocationAlwaysAndWhenInUsageDescription key. If you are supporting iOS 10 and below, add the NSLocationAlwaysUsageDescription key as well.

  1. Enter the Project Navigator view and find your Info.plist. Alternatively, click on your project icon, select your target from the sidebar or from the dropdown menu, and then select the Info tab, and open the Custom iOS Target Properties expander.

  2. Click the + next to the one of the keys. If you are using Xcode, enter in keys for Privacy - Location When In Use Usage Description, Privacy - Location Always and When In Use Usage Description, and Privacy - Location Always Usage Description. Xcode will map these keys to NSLocationWhenInUseUsageDescription, NSLocationAlwaysAndWhenInUsageDescription, and NSLocationAlwaysUsageDescription.

    Make sure the values have a type of String and enter a description for the values that will be shown when the various location permissions alerts are presented to the user. Be sure to clearly outline why you want to use their location data by providing valuable use cases specific to your app.

screenshot of location keys in Info.plist

2. Enable location monitoring

Enable location monitoring in your app some time after integrating Localytics. This will prompt the user to enable location monitoring if they haven't already been prompted. We suggest first prompting your users for WhenInUse location permission, then later using Localytics.setLocationMonitoringEnabled() to prompt for Always location permission due to the new structure of the location prompts in iOS 11.

Objective-C Swift

// your app integrates Localytics
[Localytics autoIntegrate:@"YOUR-LOCALYTICS-APP-KEY"
    withLocalyticsOptions:@{
                            LOCALYTICS_WIFI_UPLOAD_INTERVAL_SECONDS: @5,
                            LOCALYTICS_GREAT_NETWORK_UPLOAD_INTERVAL_SECONDS: @10,
                            LOCALYTICS_DECENT_NETWORK_UPLOAD_INTERVAL_SECONDS: @30,
                            LOCALYTICS_BAD_NETWORK_UPLOAD_INTERVAL_SECONDS: @90
                          }
            launchOptions:launchOptions];
// if the user already has always permissions, register them immediately
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedAlways) {
  [Localytics setLocationMonitoringEnabled:YES];
}
...
...
// your app displays a system prompt for giving the app location while the app is in the foreground
if (@available(iOS 11, *)) {
  [myLocationManager requestWhenInUseAuthorization];
}
...
...
// your app displays a system prompt for location always permissions
// make sure that your app has been granted WhenInUse permission if the user is on iOS 11
if (@available(iOS 11, *)) {
  if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedWhenInUse) {
    [myLocationManager requestAlwaysAuthorization];
  }
} else {
  [myLocationManager requestAlwaysAuthorization];
}
...
...
// check for change in permission status
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
  if (status == kCLAuthorizationStatusAuthorizedAlways) {
    // start monitoring for geofences
    [Localytics setLocationMonitoringEnabled:YES];

    ...
  }
  ...
}
// your app integrates Localytics
Localytics.autoIntegrate("YOUR-LOCALYTICS-APP-KEY", withLocalyticsOptions:[
                        LOCALYTICS_WIFI_UPLOAD_INTERVAL_SECONDS: 5,
                        LOCALYTICS_GREAT_NETWORK_UPLOAD_INTERVAL_SECONDS: 10,
                        LOCALYTICS_DECENT_NETWORK_UPLOAD_INTERVAL_SECONDS: 30,
                        LOCALYTICS_BAD_NETWORK_UPLOAD_INTERVAL_SECONDS: 90
                      ], launchOptions: launchOptions)
// if the user already has always permissions, register them immediately
if CLLocationManager.authorizationStatus() == .authorizedAlways {
  // start monitoring for geofences
  Localytics.setLocationMonitoringEnabled(true)
}

...
...
// your app displays a system prompt for giving the app location while the app is in the foreground
if #available(iOS 11.0, *) {
  myLocationManager.requestWhenInUseAuthorization()
}
...
...
// your app displays a system prompt for location always permissions
// make sure that your app has been granted WhenInUse permission if the user is on iOS 11
if #available(iOS 11.0, *) {
  if CLLocationManager.authorizationStatus() == .authorizedWhenInUse {
    myLocationManager.requestAlwaysAuthorization()
  }
} else {
  myLocationManager.requestAlwaysAuthorization()
}

...
...
// check for change in permission status
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
  if status == .authorizedAlways {
    // start monitoring for geofences
    Localytics.setLocationMonitoringEnabled(true)

    ...
  }
  ...
}

For customers who grant their users the ability to opt out of data collection, please refer to with integration in the advanced section.

3. Register for local notifications

On iOS 12+, you have the option of provisionally authorizing users to receive push notifications, even if they haven’t yet opted in to receive notifications. These provisional notifications are “delivered quietly” directly to the device Notification Center. We strongly encourage you to enable provisional authorization, as it can dramatically increase the number of users who can receive push notifications.

Provisional authorization requires that you use the UserNotifications framework. First you must import it:

Objective-C Swift

@import UserNotifications;
import UserNotifications

Then, request provisional authorization after the Localytics initialization code in didFinishLaunchingWithOptions: in your AppDelegate:

Objective-C Swift

if (NSClassFromString(@"UNUserNotificationCenter") && @available(iOS 12.0, *)) {
  UNAuthorizationOptions options = UNAuthorizationOptionProvisional;
  [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:options
            completionHandler:^(BOOL granted, NSError * _Nullable error) {
                [Localytics didRequestUserNotificationAuthorizationWithOptions:options
                            granted:granted];
  }];
}
if #available(iOS 12.0, *), objc_getClass("UNUserNotificationCenter") != nil {
    let options: UNAuthorizationOptions = [.provisional]
    UNUserNotificationCenter.current().requestAuthorization(options: options) { (granted, error) in
        Localytics.didRequestUserNotificationAuthorization(withOptions: options.rawValue, granted: granted)
    }
} 

Although Provisional Auth allows you to send notifications to users before they've opted into push, we still strongly recommend that you encourage users to fully opt in. When a user opts in, notifications will get shown on the Lock Screen and as banners, badges will be incremented, and sounds will played when a notification is received. All of this increases the prominence of the notification, increasing the chances that it will be seen by the user.

The most effect way to drive opt ins is through a soft-ask, which we discuss here. However, if you would prefer to trigger the system prompt for push authorization some other way, you can call the following:

Objective-C Swift

if (NSClassFromString(@"UNUserNotificationCenter")) {
  UNAuthorizationOptions options = (UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert);
  [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:options
                                                                      completionHandler:^(BOOL granted, NSError * _Nullable error) {
                                                                        [Localytics didRequestUserNotificationAuthorizationWithOptions:options
                                                                                                                               granted:granted];
                                                                      }];
} else {
  UIUserNotificationType types = (UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound);
  UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
  [application registerUserNotificationSettings:settings];
}
if #available(iOS 10.0, *), objc_getClass("UNUserNotificationCenter") != nil {
    let options: UNAuthorizationOptions = [.alert, .badge, .sound]
    UNUserNotificationCenter.current().requestAuthorization(options: options) { (granted, error) in
        Localytics.didRequestUserNotificationAuthorization(withOptions: options.rawValue, granted: granted)
    }
} else {
    let types: UIUserNotificationType = [.alert, .badge, .sound]
    let settings = UIUserNotificationSettings(types: types, categories: nil)
    application.registerUserNotificationSettings(settings)
}
If you are using manual integration, be sure to follow the documentation on handling the notification events.

4. Handle the opened notification

  1. Only if you are using alternative session management, handle the received UILocalNotification in your AppDelegate as follows.

    Objective-C Swift

    - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
      [Localytics handleNotification:notification.userInfo];
    }
    
    func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
      Localytics.handleNotification(notification.userInfo!)
    }
    
  2. When using the UserNotification framework and using a UNUserNotificationCenterDelegate, you need to notify the Localytics SDK when notifications are opened. Add the following to your userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: delegate method:

    Objective-C Swift

    - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler {
      [Localytics didReceiveNotificationResponseWithUserInfo:response.notification.request.content.userInfo];
      completionHandler();
    }
    
    @available(iOS 10.0, *) // if also targeting iOS versions less than 10.0
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        Localytics.didReceiveNotificationResponse(userInfo: response.notification.request.content.userInfo)
        completionHandler()
    }
    

5. Add support for categories and actions (optional)

Categories and actions on iOS are purely optional but require Localytics SDK v4.4 or higher.

Steps to integrate categories and actions:

  1. Register your desired categories and actions. These are generally configured within your App Delegate. For apps targeting iOS 10 and using the UserNotifications framework:

    Objective-C Swift

    UNNotificationAction *like = [UNNotificationAction actionWithIdentifier:@"like" title:@"Like" options:UNNotificationActionOptionForeground];
    UNNotificationAction *share = [UNNotificationAction actionWithIdentifier:@"share" title:@"Share" options:UNNotificationActionOptionForeground];
    UNNotificationCategory *social = [UNNotificationCategory categoryWithIdentifier:@"social" actions:@[like, share] intentIdentifiers:@[] options:UNNotificationCategoryOptionNone];
    
    NSSet *categories = [NSSet setWithArray:@[social]];
    [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:categories];
          
    
    let likeAction = UNNotificationAction(identifier: "like", title: "Like", options: .foreground)
    let shareAction = UNNotificationAction(identifier: "share", title: "Share", options: .foreground)
    let socialCategory = UNNotificationCategory(identifier: "social", actions: [likeAction, shareAction], intentIdentifiers: [], options: [])
    
    var categories = Set<UNNotificationCategory>()
    categories.insert(socialCategory)
    UNUserNotificationCenter.current().setNotificationCategories(categories)
          
    

    For apps targeting iOS 9 use the following:

    Objective-C Swift

    UIMutableUserNotificationAction *like = [[UIMutableUserNotificationAction alloc] init];
    like.title = @"Like";
    like.identifier = @"like";
    like.activationMode = UIUserNotificationActivationModeForeground;
    
    UIMutableUserNotificationAction *share = [[UIMutableUserNotificationAction alloc] init];
    share.title = @"Share";
    share.identifier = @"share";
    share.activationMode = UIUserNotificationActivationModeForeground;
    
    UIMutableUserNotificationCategory *social = [[UIMutableUserNotificationCategory alloc] init];
    social.identifier = @"social";
    [social setActions:@[like, share] forContext:UIUserNotificationActionContextMinimal];
    
    NSSet *categories = [NSSet setWithArray:@[social]];
    [application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:types categories:categories]];
          
    
    let like = UIMutableUserNotificationAction()
    like.title = "Like"
    like.identifier = "like"
    like.activationMode = .foreground
    
    let share = UIMutableUserNotificationAction()
    share.title = "Share"
    share.identifier = "share"
    share.activationMode = .foreground
    
    let social = UIMutableUserNotificationCategory()
    social.identifier = "social"
    social.setActions([like, share], for:.minimal)
    
    application.registerUserNotificationSettings(UIUserNotificationSettings(types:.alert, categories:[social]))
    
  2. Handle the actions when the respective action button is pressed and pass the action identifier to Localytics.

    Objective-C Swift

    -(void) userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse: UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
    
      [Localytics didReceiveNotificationResponse:response.notification.request.content.userInfo andActionIdentifier: response.actionIdentifier];
    
      if ([@"like" isEqualToString:response.actionIdentifier]) {
        //handle behavior for like button
      } else if ([@"share" isEqualToString:response.actionIdentifier]) {
        //handle behavior for share button
      } else if ([UNNotificationDefaultActionIdentifier isEqualToString:response.actionIdentifier]) {
        //handle open behavior
      } else if([UNNotificationDismissActionIdentifier isEqualToString:response.actionIdentifier]) {
        //handle dismiss behavior
      }
      if (completionHandler) {
        completionHandler();
      }
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
      Localytics.didReceiveNotificationResponse(userInfo: response.notification.request.content.userInfo, andActionIdentifier: response.actionIdentifier)
    
      switch response.actionIdentifier {
        case "like": break // handle like button
        case "share": break // handle share button
        case UNNotificationDefaultActionIdentifier: break // handle default open
        case UNNotificationDismissActionIdentifier: break // handle dismiss
        default: break // handle other cases
      }
    
      completionHandler()
    }
    
  3. If you are using manual integration and targeting iOS 9 and below, implement methods in your App Delegate to handle the actions when notifications are received.

    Objective-C Swift

    - (void) application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void (^)())completionHandler {
      [self.localyticsDelegate handleNotification:notification.userInfo withActionIdentifier:identifier];
      if (completionHandler){
        completionHandler(UIBackgroundFetchResultNoData);
      }
    }
    
    - (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification withResponseInfo:(NSDictionary *)responseInfo completionHandler:(void (^)())completionHandler {
      [self.localyticsDelegate handleNotification:notification.userInfo withActionIdentifier:identifier];
      if (completionHandler){
        completionHandler(UIBackgroundFetchResultNoData);
      }
    }
    
    func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, for notification: UILocalNotification, completionHandler: @escaping () -> Void) {
        Localytics.handleNotification(notification.userInfo!, withActionIdentifier:identifier)
    }
    
    func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, for notification: UILocalNotification, withResponseInfo responseInfo: [AnyHashable : Any], completionHandler: @escaping () -> Void) {
        Localytics.handleNotification(notification.userInfo!, withActionIdentifier:identifier)
    }
    

6. iOS 14 Location Updates

As of iOS 14.0, Apple has changed the way location permissions are handled.

iOS 14.0 introduces "precise" and "reduced" accuracy. You can control the default accuracy in your plist file. Places will not work under reduced accuracy. Your application must use precise location in order for Places to properly track location data of the user.

To ensure places still works as expected, please ensure the following changes are made to your application:

  • Ensure the following value is added to your Info.plist file to ensure the prompt defaults to requesting precise location
  • <key>NSLocationDefaultAccuracyReduced</key>
    <false/>
    
    <key>NSLocationDefaultAccuracyReduced</key>
    <false/>
    
  • Prompt the user before requesting location permissions explaining why their location data is important. Apple may potentially deny your app if it feels location tracking is too agressive.
  • If a user has manually set reduced accuracy for your app and you would like to request elevated access again, you can use the code below
  • Objective-C Swift

    // Objective-C
    [locationManager requestTemporaryFullAccuracyAuthorization:@"temporary_to_full_location_request_key"];
    
    // Info.plist
    <dict>
      <key>temporary_to_full_location_request_key</key>
      <string>Phrase explaining why precise location services are needed</string>
    </dict>
    
    // Swift
    locationManager.requestTemporaryFullAccuracyAuthorization(withPurposeKey: "temporary_to_full_location_request_key")
    
    // Info.plist
    <dict>
      <key>temporary_to_full_location_request_key</key>
      <string>Phrase explaining why precise location services are needed</string>
    </dict>
    
  • Ensure you are checking for changes in the location accuracy properly. Below is a code snippet that runs when "precise" location is enabled.
  • Objective-C Swift

    - (void)locationManagerDidChangeAuthorization:(CLLocationManager *)manager {
      if (manager.accuracyAuthorization == kCLLocationAccuracyReduced) {
        // Places will not work
      }
    }
    
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
      let accuracyAuthorization = manager.accuracyAuthorization
      if (accuracyAuthorization == .reducedAccuracy) {
        // Places will not work
      }
    }
    

Uninstall Tracking

Localytics Uninstall Tracking reports an analytics event for users that uninstall your app at high accuracy within 24 hours. Those uninstalls can be used in charts, funnels, campaign performance reports, and remarketing so you can find and address the root causes of user loss.

Before continuing, please be sure that you have completed all of the steps in Getting Started and Push Messaging and that you are using SDK version 4.0 or later (SDK v3 will only report uninstalls for iOS users with notifications enabled). You'll need a subscription to Uninstall Tracking to use this feature.

1. Register for a push token

Localytics Uninstall Tracking requires Localytics push integrated in your app. To properly track uninstalls for all users - regardless of notification permissions - you need to register for a push token as early as possible.

Basic Integration

Follow our standard guide for integrating push, ensuring you call registerForRemoteNotifications as part of the code in your app delegate’s didFinishLaunchingWithOptions method for all users.

Custom Integration

If you’re unable to use the basic integration - for example, if you have an existing integration that uses custom logic for registering for notifications later in the app’s lifecycle - you’ll need to adjust your code to separate out an early registration for push tokens independently of notification permissions.

Regardless of whether the user has been asked for notification permissions already, and whether they accept, reject, or subsequently disable notifications, you can collect a push token at app open. For Localytics to track uninstalls accurately across app upgrades and for devices with disabled notifications, you'll need to collect and send those tokens to Localytics.

If your app normally asks for notification permissions later in the app - after a soft-ask, for instance - you’ll need to separate out registering for a push token from the ask for notification permissions. To do this, put the following code in your app delegate’s didFinishLaunchingWithOptions method.

Objective-C Swift

[application registerForRemoteNotifications];
application.registerForRemoteNotifications()

For customers using manual integration, ensure that you're also passing tokens to Localytics in your application:didRegisterForRemoteNotificationsWithDeviceToken call by calling Localytics:setPushToken. For details see our manual integration guide. This is not necessary if using Localytics' automatic integration, which passes push tokens automatically after registration.

2. Ensuring Your App Ignores Uninstall Tracking Pushes

Your app delegate's application:willFinishLaunchingWithOptions: and application:didFinishLaunchingWithOptions: methods may fire when an uninstall tracking push comes in while the app is closed. You may need to ensure your app does not tag UI-related events or do background data refreshes in this situation.

The Localytics uninstall tracking push includes a key/value of localyticsUninstallTrackingPush: "true" which you can use to detect when an app launch is coming from a background uninstall tracking push.

3. Provisioning Uninstalls

Once your app's integration has been set up, contact your account manager or our support team to enable Uninstall Tracking for your app. You'll need an active subscription to Uninstalls. Let us know which Localytics apps are configured for uninstalls, along with their iOS bundle IDs.

Tracking user flow

Track screens or views within your app so you can visualize user flow within the Localytics Dashboard. We recommend exhaustively tagging every visible view in your app.

The Localytics SDK will perform duplicate suppression on two identical tagged screens that occur in a row within a single session. For example, in the set of screens {"Screen 1", "Screen 1"}, the second screen would be suppressed. However, in the set {"Screen 1", "Screen 2", "Screen 1"}, no duplicate suppression would occur.

Objective-C Swift

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [Localytics tagScreen:@"Item List"];
}
override func viewDidAppear(_ animated: Bool) {
  super.viewDidAppear(animated)
  Localytics.tagScreen("Item List")
}

Tracking revenue

There are two ways to think about Lifetime Value (LTV) in Localytics: monetary and non-monetary. If your app allows real currency transactions, our Dashboard can show LTV in USD. If your app uses virtual currencies like coins or tokens, you can configure your app to report LTV as the raw data you pass in.

You can configure each Mobile App in Localytics to have either a monetary value or raw data for Lifetime Value:

Tracking Monetary LTV

If you'd like to track LTV as monetary-based revenue, you should increment the value upon the completion of a purchase by the purchase amount. Make sure to configure your app in Localytics to show LTV as "Tracked as Money (US cents)".

LTV must be an integer amount, and the Localytics system requires you pass the number of USD cents as the LTV value in order to track money. For example, if the purchase amount is "USD $2.99", you should pass the integer "299" as the LTV. If the cents don't matter to you, feel free to round up to whole dollar values, but continue to pass the value as cents. If you want to track the rounded value of "USD $3.00", you should pass "300" as the value.

Currently, Localyics only allows LTV tracking in USD. If you want to track other currencies, you could convert the monetary amount to USD on the device before sending to Localytics.

Tracking Non-Monetary LTV

Another way to measure LTV is to track a non-monetary value important to your app. Examples include the number seconds spent engaged with content, or the amount of virtual currency earned. To track these values, send the corresponding integer value to Localytics. Make sure to configure your app in Localytics to show LTV as "Raw Value". Otherwise, Localytics will automatically divide your values by 100 and display a "$" in front of the values in the Dashboard.

LTV Examples

Increment user lifetime value (LTV) on any event using the optional LTV incrementer parameter as seen below.

You can increment LTV with our standard purchased and completed checkout events as follows. The item price and total price values will be used respectively.

Objective-C Swift

[Localytics tagPurchased:@"Shirt"
                  itemId:@"sku-123"
                itemType:@"Apparel"
               itemPrice:@15
              attributes:extraAttributes];
[Localytics tagCompletedCheckout:@50
                       itemCount:@2
                      attributes:extraAttributes];
Localytics.tagPurchased("Shirt", itemId: "sku-123", itemType: "Apparel", itemPrice: 15, attributes: extraAttributes)
Localytics.tagCompletedCheckout(50, itemCount: 2, attributes: extraAttributes)

You can also increment LTV using a custom event by including a customerValueIncrease value.

Objective-C Swift

[Localytics tagEvent:@"Item Purchased"
          attributes:@{@"Item Name" : @"Stickers", @"Aisle" : @"Knick-Knacks"}
   customerValueIncrease:@499];
Localytics.tagEvent("Item Purchased", attributes: ["Item Name": "Stickers", "Aisle": "Knick-Knacks"], customerValueIncrease: 499)

Setting custom dimensions

Custom dimensions are special fields that are used for splitting and filtering data within the Localytics Dashboard and are useful for storing user-level information. Custom dimensions are like sticky event attributes in the sense that once their value is set, it remains set until the value is changed again. Unlike event attributes, custom dimension values are attached to all three types of datapoints (session start, session close, and event) the same way that standard dimensions are which makes their values available within almost every report in the Localytics Dashboard.

Your app can have up to 20 custom dimensions, and they will be indexed between 0 and 19. Name all of your custom dimensions in the Localytics Dashboard > Settings > Apps > (find app) > Gear icon > Custom Dimensions.

Whenever a datapoint is created, all custom dimension values are attached to the datapoint. Therefore, it is ideal to set custom dimensions as soon as their value is known in all code paths in your app. It is not uncommon to use an analytics callback to set custom dimension values before the start of a session (and the creation of a "session start" datapoint).

Setting a value

Objective-C Swift

[Localytics setValue:@"Trial" forCustomDimension:0];
Localytics.setValue("Trial", forCustomDimension: 0)

Initializing dimensions before session start

Set the value of all custom dimensions as early as possible in your app's code path even if you only set the value to "Not applicable" or "Not available". Having at least some value for each custom dimension instead of nothing will prevent the display of "[Unspecified]" values in the Dashboard and help to make it much more obvious to distinguish intentionally missing values from unintentionally missing values.

  1. In your app delegate header file, ensure that your app delegate conforms to the LLAnalyticsDelegate protocol as follows.

    Objective-C Swift

    @interface YourAppDelegate : UIResponder <UIApplicationDelegate, LLAnalyticsDelegate>
    
    class AppDelegate: UIResponder, UIApplicationDelegate, LLAnalyticsDelegate {
    
  2. Modify your app delegate as follows to register the Localytics analytics delegate and set any custom dimension values before the first session opens. You will need to modify the custom dimension code below to match your exact custom dimension values.

    Objective-C Swift

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // ...Localytics initialization code here...
    
        [Localytics setAnalyticsDelegate:self];
    
        // ... rest of didFinishLaunchingWithOptions ...
    
        return YES;
    }
    
    - (void)localyticsSessionWillOpen:(BOOL)isFirst isUpgrade:(BOOL)isUpgrade isResume:(BOOL)isResume {
        if (isFirst) {
            [Localytics setValue:@"Logged Out" forCustomDimension:0];
            [Localytics setValue:@"0" forCustomDimension:1];
            // ...rest of custom dimensions go here...
        }
    }
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
      // ...Localytics initialization code here...
    
      Localytics.setAnalyticsDelegate(self)
    
      // ... rest of didFinishLaunchingWithOptions ...
    
      return true
    }
    
    func localyticsSessionWillOpen(_ isFirst: Bool, isUpgrade: Bool, isResume: Bool) {
      if isFirst {
        Localytics.setValue("Logged Out", forCustomDimension: 0)
        Localytics.setValue("0", forCustomDimension: 1)
        // ...rest of custom dimensions go here...
      }
    }
    

Clearing a value

Though it may be more appropriate to set a custom dimension value back to an initial state, sometimes you may need to completely clear the value. Setting an an empty string will NOT clear the value. To clear a value use the following code:

Objective-C Swift

[Localytics setValue:nil forCustomDimension:0];
Localytics.setValue(nil, forCustomDimension:0)

Tracking user acquisition source

Use Localytics to track the origin of your new users and attribute them to a specific acquisition source. Localytics employs a variety of attribution methods, but at the core, all methods work by matching a shared identifier from both before and after app installation.

In order for your app to be able to access the device identifiers that are required for most iOS acquisition tracking to work, you must include AdSupport.framework as discussed elsewhere in this guide. You've likely already done this via CocoaPods, Carthage, or the manual installation approach, but it's worth checking just to be sure.

Before you release

Before releasing your app with Localytics, you'll want to ensure all features of the integration are configured properly.

1. Setup a production app key

In order to run the Localytics SDK, you must initialize the SDK using your Localytics app key. You can find your app key in the Localytics Dashboard.

2. Disable Localytics logging

Logging is disabled by default, but you'll want to make sure it is not enabled in your production build.

Objective-C Swift

[Localytics setLoggingEnabled:NO];
Localytics.setLoggingEnabled(false)

3. Ensure test mode is setup on production build

We strongly recommend that you set up test mode. It's important for other users of your Localytics instance to test marketing campaigns—like push, in-app, and inbox messages—before and after the app is released. It's also valuable for future troubleshooting from Localytics.

To configure test mode, you'll need to setup a URL scheme. The URL scheme is defined as "amp" followed by your Localytics app key, as demonstrated below. You can find your app key in the Localytics Dashboard. If you switch between multiple Localytics app keys, as when you have separate testing and production app keys, there's no harm in registering both URL schemes.

Configure test mode URL scheme in Project Settings
  1. Enter Project Navigator view and click on your project icon. It should be at the top of the Project Navigator.
  2. Select your target from the sidebar or from the dropdown menu, then select the Info tab.
  3. Open the URL Types expander and click +.
  4. In the URL Schemes field, enter "amp" followed by your Localytics app key.

4. Ensure push notifications work on production build

Before you release your app to the App Store, you will want to test that it can properly receive not only development push messages as above, but also production push messages. We also recommend using a different Localytics app key for the production build of your app.

  1. If your production build uses a different App ID/Bundle ID than your development build, follow the creating a universal certificate and verifying your App ID is push enabled instructions to generate a universal push notification certificate for this App ID.
  2. Upload your universal push certificate to Localytics for your production app key. Follow the uploading your push certificate instructions but select Production from the Push Mode dropdown menu. You can use the same certificate (.p12) if your production build uses the same App ID/Bundle ID as your development build.
  3. Create a distribution provisioning profile. Follow the instructions for creating Ad Hoc provisioning profiles. Ad Hoc builds are just like App Store builds except that you can only install them on a limited set of predetermined devices. Both types of builds will use Apple's Production Push environment.
  4. Generate a distribution build of your app and install it on a connected test device. If you have logging enabled at this point, you should see messages indicating that the Localytics SDK obtained your push token and should see the push token present in the push (not dpush) field of the next upload body.
  5. Send yourself a test push message using the same instructions as before.
  6. Complete all of the same steps above except select App Store as the provisioning profile type. If you are using multiple Localytics app keys, e.g. one for development and one for production, make sure to upload your production push certificate to your production app key.

5. QA analytics on production build

Use a utility such as Charles Proxy to watch the data transmit from your device to Localytics. You should check all analytics tags, such as Events and Customer ID, are being sent with the correct values.

Advanced

Manual Integration

The standard Localytics session management approach relies on the Localytics SDK to automatically listen for indications of state change (e.g. foreground / background transitions) in your app and then to track the state change accordingly. Most apps are able to use the standard approach. If you are unable to use the standard session management approach, the instructions below will help you to initialize the SDK to achieve the same result as the standard approach.

Use the following initialization instructions instead of those in Getting started. If you have an app with extensive, engaged user activity in the background, as is common with streaming media apps, you may find it necessary reposition calls to openSession and closeSession to achieve the session behavior you desire. Enabling logging when modifying session behavior is often very helpful.

Note that the push notification code will not do anything unless you have also followed the push setup instructions.

In your application's delegate file:

  1. Import the Localytics SDK under any existing imports.

    Objective-C Swift

    @import Localytics;
    
    import Localytics
    
  2. Add the following code to the start of application:didFinishLaunchingWithOptions:.

    Objective-C Swift

    [Localytics integrate:@"YOUR-LOCALYTICS-APP-KEY"
    withLocalyticsOptions:@{
                            LOCALYTICS_WIFI_UPLOAD_INTERVAL_SECONDS: @5,
                            LOCALYTICS_GREAT_NETWORK_UPLOAD_INTERVAL_SECONDS: @10,
                            LOCALYTICS_DECENT_NETWORK_UPLOAD_INTERVAL_SECONDS: @30,
                            LOCALYTICS_BAD_NETWORK_UPLOAD_INTERVAL_SECONDS: @90
                            }
            launchOptions:launchOptions];
    
    if (application.applicationState != UIApplicationStateBackground) {
        [Localytics openSession];
    }
    
    Localytics.integrate("YOUR-LOCALYTICS-APP-KEY", withLocalyticsOptions: [
        LOCALYTICS_WIFI_UPLOAD_INTERVAL_SECONDS: 5,
        LOCALYTICS_GREAT_NETWORK_UPLOAD_INTERVAL_SECONDS: 10,
        LOCALYTICS_DECENT_NETWORK_UPLOAD_INTERVAL_SECONDS: 30,
        LOCALYTICS_BAD_NETWORK_UPLOAD_INTERVAL_SECONDS: 90
        ])
    
    if application.applicationState != .background {
      Localytics.openSession()
    }
    
  3. Add the following code to the start of both applicationDidBecomeActive: and applicationWillEnterForeground:.

    Objective-C Swift

    [Localytics openSession];
    [Localytics upload];
    
    Localytics.openSession()
    Localytics.upload()
    

    This will open or resume a Localytics session and uploads any outstanding events and sessions in the cache.

  4. Add the following code to the start of applicationDidEnterBackground:.

    Objective-C Swift

    [Localytics dismissCurrentInAppMessage];
    [Localytics closeSession];
    [Localytics upload];
    
    Localytics.dismissCurrentInAppMessage()
    Localytics.closeSession()
    Localytics.upload()
    

    This code is triggered every time your app is backgrounded. It ensures any currently showing in-apps are performance-tagged and dismissed, starts a session timeout, and uploads any outstanding events and sessions in the cache. If the app terminates before the completion of the upload, the upload is retried on the user's next session.

  5. Add the following code to the end of whichever deeplinking method is supported by your version of iOS. To support iOS 8 use application:openURL:sourceApplication:annotation:. To support iOS 9 and up use application:openURL:options:

    Objective-C Swift

    return [Localytics handleTestModeURL:url];
    
    return Localytics.handleTestModeURL(url)
    

    This handles deep linking into your app. It also allows Localytics to see if the deep link is intended to trigger Rapid Push Verification or campaign test mode for previewing push and in-app campaigns.

  6. Add the following code to the end of application:didRegisterForRemoteNotificationsWithDeviceToken:.

    Objective-C Swift

    [Localytics setPushToken:deviceToken];
    
    Localytics.setPushToken(deviceToken)
    

    This delivers new and updated push tokens to Localytics for this device once they're received. You'll also need to ask the OS to get the token at app open first (see "Register for a push token").

  7. Add the following code to the end of application:didFailToRegisterForRemoteNotificationsWithError:.

    Objective-C Swift

    NSLog(@"Failed to register for remote notifications: %@", [error description]);
    
    print("Failed to register for remote notifications: \(error.localizedDescription)")
    

    In the event that a request for push token fails, this step will log that failure. The reason for this failure could be due to problems with either "push setup and provisioning" or "push integration in your application" in the Push messaging section.

  8. Add the following code to the end of application:didReceiveRemoteNotification:fetchCompletionHandler:.

    Objective-C Swift

    if (@available(iOS 10.0, *)) {
        [Localytics handleNotificationReceived:userInfo];
    } else {
        [Localytics handleNotification:userInfo];
    }
    completionHandler(UIBackgroundFetchResultNoData);
    
    if (#available(iOS 10.0, *)) {
        Localytics.handleNotificationReceived(userInfo!)
    } else {
        Localytics.handleNotification(userInfo!)
    }
    completionHandler(.noData)
    

    Only if your app delegate cannot implement the fetchCompletionHandler version of this method:

    1. Add the following code to the end of application:didReceiveRemoteNotification:.

      Objective-C Swift

      if (@available(iOS 10.0, *)) {
          [Localytics handleNotificationReceived:userInfo];
      } else {
          [Localytics handleNotification:userInfo];
      }
      
      if (#available(iOS 10.0, *)) {
          Localytics.handleNotificationReceived(userInfo!)
      } else {
          Localytics.handleNotification(userInfo!)
      }
      
    2. Add the following code to the end of application:didFinishLaunchingWithOptions:.

      Objective-C Swift

      if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {
          [Localytics handleNotification:[launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]];
      }
      
      if let notificationInfo = launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] as? [AnyHashable: Any] {
          Localytics.handleNotification(notificationInfo)
      }
      

    This tags push opened events for performance reporting, and handles opening any deeplinks in the notification.

  9. This step is only required if you are using the UserNotification framework introduced in iOS 10 and you are setting a UNUserNotificationCenterDelegate. If this doesn't apply to your app, please continue on to the next step.

    When using the UserNotification framework and using a UNUserNotificationCenterDelegate, you need to notify the Localytics SDK when notifications are opened. Add the following to your userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: delegate method:

    Objective-C Swift

    - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler {
      [Localytics didReceiveNotificationResponseWithUserInfo:response.notification.request.content.userInfo];
      completionHandler();
    }
    
    @available(iOS 10.0, *) // if also targeting iOS versions less than 10.0
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        Localytics.didReceiveNotificationResponse(userInfo: response.notification.request.content.userInfo)
        completionHandler()
    }
    
  10. If you are targeting versions below iOS 10 or are not using UserNotifications as indicated above, include the following:

    1. Add the following code to the end of application:didFinishLaunchingWithOptions:.

      Objective-C Swift

      if (launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]) {
          [Localytics handleNotification:launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]];
      }
      
      if let notificationInfo = launchOptions?[UIApplicationLaunchOptionsKey.localNotification] as? [AnyHashable: Any] {
          Localytics.handleNotification(notificationInfo)
      }
      

      This will tag a push opened events and handle deep links if the app wasn't already in memory when the notification was clicked.

    2. Add the following code to the end of application:didReceiveLocalNotification:.

      Objective-C Swift

      [Localytics handleNotification:notification.userInfo];
      
      Localytics.handleNotification(notification.userInfo!)
      

      This step ensures correct tagging of Places push opened as well as Places deep linking capabilities.

    3. Add the following code to the end of application:didRegisterUserNotificationSettings:.

      Objective-C Swift

      [Localytics didRegisterUserNotificationSettings];
      
      Localytics.didRegisterNotificationSettings()
      

      This step will allow Localytics to track whether a user has enabled notifications.

  11. Ensure that there is not any Localytics session-related code in applicationWillResignActive:. This will only apply if you have an older Localytics integration.

  12. Compile and run your app.

Swift

The Localytics SDK is written in Objective-C but to simplify the process of integrating it with Swift the public APIs have been annotated with nullable and nonnull annotations, collections take advantage of light weight generics, and the SDK is available as a dynamic framework. Calling SDK methods is a straight forward translation from bracket notation to dot notation.

Access to the original AppDelegate

If you are adding methods to the AppDelegate, for access from other parts of the application, it is necessary to take additional steps in Swift. Add the following code to the AppDelegate:

class AppDelegate: UIResponder, UIApplicationDelegate {
    static var originalAppDelegate:AppDelegate!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    AppDelegate.originalAppDelegate = self
        

This will make a globally accessible variable that points to the original AppDelegate. It can be used as follows from anywhere in the application:

AppDelegate.originalAppDelegate.someMethod()

Privacy

To support privacy regulations such as the EU’s General Data Protection Regulation (GDPR), as well as our customers’ unique privacy policy requirements, Localytics provides various methods, tools, or controls to assist our customers in meeting their obligations.

There are additional considerations to take into account to ensure proper handling across devices, applications, and digital properties. Please be sure to reference all documentation, and consult with your product, privacy, and legal teams to ensure your implementation of our products meets your unique privacy and regulatory requirements. Localytics Support and Services teams may be engaged to assist, and you can refer to Localytics Privacy Approach documentation for additional context.

Opting users out

Many apps allow their users the ability to opt out of data collection. In order to stop the Localytics SDK from collecting any additional data, customers can call:

Objective-C Swift

[Localytics setOptedOut:YES];
Localytics.setOptedOut(true)

Any device that has opted out in Localytics will immediately close the current session (if there is one) and tag an opt out event indicating that the device is no longer reporting any future data. Any subsequent calls that would generate a datapoint (tagEvent and setProfileAttribute for example) will be dropped. Additional detail is available in this Localytics Help article.

The opt out setting in Localytics is device specific, so if your app supports multiple users, you may want to trigger a call to setOptedOut every time a user logs in or out to update the Localytics SDK based on their previous preference.

For customers integrated with SDK versions below 5.1, calling setOptedOut will not prevent profile attributes from being set or updated.

Suppress data collection until end-user opt in

For some apps, it may be preferable to not collect any data until a user explicitly opts into data collection, or end-user consent is verified. In order to accomplish this, the app will need to update its integration such as the following:

Objective-C Swift

//Code from AppDelgate
[Localytics integrate:@"YOUR-LOCALYTICS-APP-KEY"
withLocalyticsOptions:@{
                        LOCALYTICS_WIFI_UPLOAD_INTERVAL_SECONDS: @5,
                        LOCALYTICS_GREAT_NETWORK_UPLOAD_INTERVAL_SECONDS: @10,
                        LOCALYTICS_DECENT_NETWORK_UPLOAD_INTERVAL_SECONDS: @30,
                        LOCALYTICS_BAD_NETWORK_UPLOAD_INTERVAL_SECONDS: @90
                        }
        launchOptions:launchOptions];
// The following method should reference a boolean value in your app that determines if the user has opted into
// data collection or not. The default (if never asked) should be false.
if (![self isUserOptedIntoDataCollection]) {
  [Localytics setOptedOut:YES];
}
//Code from AppDelgate
Localytics.autoIntegrate("YOUR-LOCALYTICS-APP-KEY",
                         withLocalyticsOptions:[
                            LOCALYTICS_WIFI_UPLOAD_INTERVAL_SECONDS: 5,
                            LOCALYTICS_GREAT_NETWORK_UPLOAD_INTERVAL_SECONDS: 10,
                            LOCALYTICS_DECENT_NETWORK_UPLOAD_INTERVAL_SECONDS: 30,
                            LOCALYTICS_BAD_NETWORK_UPLOAD_INTERVAL_SECONDS: 90
                        ],
                         launchOptions: launchOptions)
// The following method should reference a boolean value in your app that determines if the user has opted into
// data collection or not. The default (if never asked) should be false.
if (!self.isUserOptedIntoDataCollection()) {
  Localytics.setOptedOut(true);
}

This effectively suppresses the integration until consent is verified.

Methods to support GDPR and other privacy requirements

The Localytics SDK provides the following APIs in SDK v5.1+:

  • [Localytics setPrivacyOptedOut:YES] This API will delete all of the end user's data in addition to preventing any further data from being collected. This method differs from setOptedOut in that it will additionally cause Localytics to trigger a user's Right To Be Forgotten request in accordance with GDPR. This method is intended to support end user requests to be forgotten, cease data collection, and delete all historical data about the end user. For additional details, please refer to this help document.
  • [Localytics pauseDataUploading:YES] This API will pause any data uploading to Localytics while still collecting information about the end user. It is particularly important to prevent data from being uploaded while the data collection opt-in status of an end user is still unknown. You might want to use this method if your app is configured to solicit user consent on first launch. In this case, you'd want to pause data upload until consent is granted. Once granted, the application can commence with uploads as normal. If consent is declined, you'll want to configure the app to opt-out of data collection as appropriate.
  • [Localytics setCustomerId:@"CID" privacyOptedOut:YES] This API will update the current customer ID and immediately update the data collection opt-out status. This method may be used to support scenarios such as an application that, during login will verify consent via your back-end, and determine their 'forgotten' status. In that scenario this API may be called to set that user’s consent status as appropriate. We recommend you verify the the opt-in status of every user who can sign in to your application.
For customers on SDK versions below 5.1 who do not have the ability to upgrade to 5.1, there is sample code available that may assist in integration.

Getting Started

Because many customers allow their end users to grant/revoke consent through other services outside the client-side application, such as a web page Localytics expects customers to manage data collection opt-out settings for all client-side integrations. As a result, there are additional steps required during the integration process to ensure proper data handling.

While determining the opt-out status of an end user, it is expected that all data uploading should be paused. If the end-user has opted out, then all of the data will be deleted and no upload will occur. If the end-user is not opted out, then all of the data collected will be uploaded and the Localytics SDK can continue to function as normal.

Objective-C Swift

[Localytics pauseDataUploading:YES];
[Localytics integrate:@"YOUR-LOCALYTICS-APP-KEY"
withLocalyticsOptions:@{
                        LOCALYTICS_WIFI_UPLOAD_INTERVAL_SECONDS: @5,
                        LOCALYTICS_GREAT_NETWORK_UPLOAD_INTERVAL_SECONDS: @10,
                        LOCALYTICS_DECENT_NETWORK_UPLOAD_INTERVAL_SECONDS: @30,
                        LOCALYTICS_BAD_NETWORK_UPLOAD_INTERVAL_SECONDS: @90
                        }
        launchOptions:launchOptions];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  //This will be available even when the end user is opted out.
  NSString *customerId = [Localytics customerId];
  //Make a request to your servers to see if the end user is opted out.
  BOOL isUserOptedOutOnServer;
  [Localytics setPrivacyOptedOut:isUserOptedOutOnServer];
  [Localytics pauseDataUploading:NO];
});
Localytics.pauseDataUploading(true)
Localytics.autoIntegrate("YOUR-LOCALYTICS-APP-KEY",
                         withLocalyticsOptions:[
                            LOCALYTICS_WIFI_UPLOAD_INTERVAL_SECONDS: 5,
                            LOCALYTICS_GREAT_NETWORK_UPLOAD_INTERVAL_SECONDS: 10,
                            LOCALYTICS_DECENT_NETWORK_UPLOAD_INTERVAL_SECONDS: 30,
                            LOCALYTICS_BAD_NETWORK_UPLOAD_INTERVAL_SECONDS: 90
                        ],
                         launchOptions: launchOptions)

DispatchQueue(label: "com.example.privacy-check").async {
    //This will be available even when the end user is opted out.
    var customerId = Localytics.customerId()
    //Make a request to your servers to see if the end user is opted out.
    var isUserOptedOutOnServer
    Localytics.setPrivacyOptedOut(isUserOptedOutOnServer)
    Localytics.pauseDataUploading(false)
}

Authentication

Customers who have authentication in their apps should ensure the privacy opt-out status of end users when they log in.

To log an end user in you should pause all data uploads until you determine data collection opt-out settings for the new user. Additionally, to ensure privacy, we encourage customers to use a new API introduced in SDK v5.1+ that accepts the privacy opt-out status of the user being logged in:

Objective-C Swift

[Localytics pauseDataUploading:YES];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //Make a request to your servers to retrieve the end user's customer id and opted out status.
    NSString *customerId;
    BOOL isUserOptedOutOnServer;
    [Localytics setCustomerId:customerId privacyOptedOut:isUserOptedOutOnServer];
    [Localytics pauseDataUploading:NO];
});
Localytics.pauseDataUploading(true)
DispatchQueue(label: "com.example.privacy-check").async {
    //Make a request to your servers to see if the end user's customer ID and opted out status.
    var customerId
    var isUserOptedOutOnServer
    Localytics.setCustomerId(customerId, privacyOptedOut:isUserOptedOutOnServer)
    Localytics.pauseDataUploading(false)
}

Similarly, to log an end user out you can continue to set the customer ID to nil. If the new user should resume collecting data, make sure to opt back into data collection:

Objective-C Swift

[Localytics setCustomerId:nil privacyOptedOut:NO];
Localytics.setCustomerId(nil, privacyOptedOut:false)

Implications for messaging

Forgetting about an end user via the setPrivacyOptedOut API implies that the end user will no longer receive targeted messaging based on their behavior, profile, or personalization data, etc. Therefore, forgetting an end user will initiate the following behavior:

  • Forgotten end users will not be targetable by campaigns with an audience criteria. Localytics will begin filtering out forgotten end users from any future campaigns within 8 hours of their request to be forgotten.
  • Forgotten end users will no longer be sent Push campaigns. All data for forgotten end users will have been deleted, including their push tokens, so they will become untargetable.
  • Forgotten end users will still be able to see messaging campaigns they had previously been sent.
  • Historical behavioral data will be deleted for forgotten users so no future sends or conversions will be generated for these users.
  • No customer data should be reported to any external services from In-App or Inbox campaigns that are broadcast to all users. In accordance with this, the ADID collected by the Localytics SDK will never be appended to call to action URLs for forgotten users.
  • A forgotten user can opt back into being remembered using setPrivacyOptedOut. In this case, all data collected prior to opting in remains forgotten and data collection will start from scratch.

Implications for Places

Localytics Places messaging feature takes advantage of location services provided by the OS to display notifications. While this doesn't break any agreement made about data collection (because geofencing is handled by the OS), end users that have requested to be forgotten and opted-out may feel that location tracking is a breach of their privacy. As such, Localytics SDK will, by default, disable Places monitoring when an end user is privacy opted-out. Localytics expects our customers to determine if Places should continue to be enabled when a user is opted out of data collection. If so, we expect that this is explicitly communicated to that end user.

By default, when opting an end user out of data collection, the Localytics SDK will turn off Places monitoring. As a result, it is expected that any customer who uses Places only does so if the customer is opted into data collection. A sample can be found demonstrating proper integration in our samples repo.

If the customer prefers to enable Places, location-based messaging, even when the end user has opted out of data collection, then this should be explicitly communicated to the end user, and Places should be re-enabled after any call to [Localytics setPrivacyOptedOut].

Callbacks

Analytics callbacks

These analytics callbacks are useful for setting the value of a custom dimension or profile attribute before a session opens, firing an event at the beginning or end of a session, or taking action in your app based on an auto-tagged campaign performance event in the Localytics SDK. All Localytics analytics callbacks are called on an internal background thread.

  1. In your app delegate header file, ensure that your app delegate conforms to the LLAnalyticsDelegate protocol as follows.

    Objective-C Swift

    @interface YourAppDelegate : UIResponder <UIApplicationDelegate, LLAnalyticsDelegate>
    
    class AppDelegate: UIResponder, UIApplicationDelegate, LLAnalyticsDelegate {
    
  2. Modify your app delegate as follows to register the Localytics analytics delegate and implement any of the analytics callbacks you require.

    Objective-C Swift

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // ...Localytics initialization code here...
    
        [Localytics setAnalyticsDelegate:self];
    
        // ... rest of didFinishLaunchingWithOptions ...
    
        return YES;
    }
    
    - (void)localyticsSessionWillOpen:(BOOL)isFirst
                            isUpgrade:(BOOL)isUpgrade
                             isResume:(BOOL)isResume {
        // ... do something ...
    }
    
    - (void)localyticsSessionDidOpen:(BOOL)isFirst
                           isUpgrade:(BOOL)isUpgrade
                            isResume:(BOOL)isResume {
        // ... do something ...
    }
    
    - (void)localyticsDidTagEvent:(NSString *)
                       attributes:(NSDictionary *)attributes
            customerValueIncrease:(NSNumber *)customerValueIncrease {
        // ... do something ...
    }
    
    - (void)localyticsSessionWillClose {
        // ... do something ...
    }
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
      // ...Localytics initialization code here...
    
      Localytics.setAnalyticsDelegate(self)
    
      // ... rest of didFinishLaunchingWithOptions ...
    
      return true
    }
    
    func localyticsSessionWillOpen(_ isFirst: Bool, isUpgrade: Bool, isResume: Bool) {
      // ... do something ...
    }
    
    func localyticsSessionDidOpen(_ isFirst: Bool, isUpgrade: Bool, isResume: Bool) {
      // ... do something ...
    }
    
    func localyticsDidTagEvent(_ eventName: String, attributes: [String : String]? = nil, customerValueIncrease: NSNumber?) {
      // ... do something ...
    }
    
    func localyticsSessionWillClose() {
      // ... do something ...
    }
    

Messaging callbacks

Messaging callbacks are useful for understanding when Localytics will display messaging campaigns. This can help you prevent conflicts with other views in your app, as well as potentially suppress the Localytics display. All Localytics messaging callbacks are called on the main thread.

  1. In your app delegate header file, ensure that your app delegate conforms to the LLMessagingDelegate protocol as follows.

    Objective-C Swift

    @interface YourAppDelegate : UIResponder <UIApplicationDelegate, LLMessagingDelegate>
    
    class AppDelegate: UIResponder, UIApplicationDelegate, LLMessagingDelegate {
    
  2. Modify your app delegate as follows to register the Localytics messaging delegate and implement any of the analytics callbacks you require.

    Objective-C Swift

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // ...Localytics initialization code here...
    
        [Localytics setMessagingDelegate:self];
    
        // ... rest of didFinishLaunchingWithOptions ...
    
        return YES;
    }
    
    - (BOOL)localyticsShouldShowInAppMessage:(nonnull LLInAppCampaign *)campaign {
        return YES; // return NO to suppress the in-app.
    }
    
    - (BOOL)localyticsShouldDelaySessionStartInAppMessages {
        return NO; // return YES to delay the triggering of messages set to display on session start.
        // Later, to trigger these messages call Localytics.triggerSessionStartInAppMessages().
    }
    
    - (nonnull LLInAppConfiguration *)localyticsWillDisplayInAppMessage:(nonnull LLInAppCampaign *)campaign withConfiguration:(nonnull LLInAppConfiguration *)configuration {
        // ... optionally modify the aspect ratio for center in-app creatives, offset for banner in-app creatives and background alpha ...
        return configuration;
    }
    
    - (void)localyticsDidDisplayInAppMessage {
        // ... do something ...
    }
    
    - (void)localyticsWillDismissInAppMessage {
        // ... do something ...
    }
    
    - (void)localyticsDidDismissInAppMessage {
        // ... do something ...
    }
    
    - (void)localyticsWillDisplayInboxDetailViewController {
        // ... do something ...
    }
    
    - (void)localyticsDidDisplayInboxDetailViewController {
        // ... do something ...
    }
    
    - (void)localyticsWillDismissInboxDetailViewController {
        // ... do something ...
    }
    
    - (void)localyticsDidDismissInboxDetailViewController {
        // ... do something ...
    }
    
    - (BOOL)localyticsShouldDisplayPlacesCampaign:(nonnull LLPlacesCampaign *)campaign {
        return YES; // return NO to suppress the notification
    }
    
    /*
     * If your app is using the UserNotification framework and the device is running iOS 10 or later,
     * localyticsWillDisplayNotificationContent:forPlacesCampaign will be called instead.
     */
    - (nonnull UILocalNotification *)localyticsWillDisplayNotification:(nonnull UILocalNotification *)notification forPlacesCampaign:(nonnull LLPlacesCampaign *)campaign {
        // ... optionally modify the title, sound, or badge ...
        return notification;
    }
    
    /*
     * Only called if your app is using the UserNotification framework and the device is running iOS 10 or later.
     * Otherwise, localyticsWillDisplayNotification:forPlacesCampaign will be called.
     */
    - (nonnull UNMutableNotificationContent *)localyticsWillDisplayNotificationContent:(nonnull UNMutableNotificationContent *)notification forPlacesCampaign:(nonnull LLPlacesCampaign *)campaign {
        // ... optionally modify the title, sound, body, etc. ...
        return notification;
    }
    
    - (BOOL)localyticsShouldDeeplink:(nonnull NSURL *)url {
      return YES; // return NO to stop Localytics from deeplinking
    }
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
      // ...Localytics initialization code here...
    
      Localytics.setMessagingDelegate(self)
    
      // ... rest of didFinishLaunchingWithOptions ...
    
      return true
    }
    
    func localyticsShouldShow(inAppMessage campaign: LLInAppCampaign) -> Bool {
        return true; // return false to suppress the in-app.
    }
    
    func localyticsShouldDelaySessionStartInAppMessages() -> Bool {
        return false; // return true to delay the triggering of messages set to display on session start.
    }
    
    func localyticsWillDisplay(inAppMessage campaign: LLInAppCampaign, with configuration: LLInAppConfiguration) -> LLInAppConfiguration {
        // ... optionally modify the aspect ratio for center in-app campaigns, offset for banner in-app campaigns and background alpha ...
        return configuration;
    }
    
    func localyticsDidDisplayInAppMessage() {
      // ... do something ...
    }
    
    func localyticsWillDismissInAppMessage() {
      // ... do something ...
    }
    
    func localyticsDidDismissInAppMessage() {
      // ... do something ...
    }
    
    func localyticsWillDisplayInboxDetailViewController() {
      // ... do something ...
    }
    
    func localyticsDidDisplayInboxDetailViewController() {
      // ... do something ...
    }
    
    func localyticsWillDismissInboxDetailViewController() {
      // ... do something ...
    }
    
    func localyticsDidDismissInboxDetailViewController() {
      // ... do something ...
    }
    
    func localyticsShouldDisplay(_ campaign: LLPlacesCampaign) -> Bool {
      return true // return false to suppress the notification
    }
    
    /*
     * If your app is using the UserNotification framework and the device is running iOS 10 or later,
     * localyticsWillDisplayNotificationContent:forPlacesCampaign will be called instead.
     */
    func localyticsWillDisplay(_ notification: UILocalNotification, for campaign: LLPlacesCampaign) -> UILocalNotification {
      // ... optionally modify the title, sound, or badge ...
      return notification
    }
    
    /*
     * Only called if your app is using the UserNotification framework and the device is running iOS 10 or later.
     * Otherwise, localyticsWillDisplayNotification:forPlacesCampaign will be called.
     */
     @available(iOS 10.0, *)
     func localyticsWillDisplay(_ notification: UNMutableNotificationContent, for campaign: LLPlacesCampaign) -> UNMutableNotificationContent {
      // ... optionally modify the title, sound, body, etc. ...
      return content
    }
    
    func localyticsShouldDeeplink:(_ url: NSURL) -> Bool {
      return true; // return false to stop Localytics from deeplinking
    }
    

Location callbacks

Location callbacks are useful for understanding when Localytics responds location changes. localyticsDidUpdateLocation is called on an internal background thread; the others are called on the main thread.

  1. In your app delegate header file, ensure that your app delegate conforms to the LLLocationDelegate protocol as follows.

    Objective-C Swift

    @interface YourAppDelegate : UIResponder <UIApplicationDelegate, LLLocationDelegate>
    
    class AppDelegate: UIResponder, UIApplicationDelegate, LLLocationDelegate {
    
  2. Modify your app delegate as follows to register the Localytics messaging delegate and implement any of the analytics callbacks you require.

    Objective-C Swift

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // ...Localytics initialization code here...
    
        [Localytics setLocationDelegate:self];
    
        // ... rest of didFinishLaunchingWithOptions ...
    
        return YES;
    }
    
    - (void)localyticsDidUpdateLocation:(nonnull CLLocation *)location {
        // ... do something
    }
    
    - (void)localyticsDidUpdateMonitoredRegions:(nonnull NSArray<LLRegion *> *)addedRegions removeRegions:(nonnull NSArray<LLRegion *> *)removedRegions {
        // ... do something
    }
    
    - (void)localyticsDidTriggerRegions:(nonnull NSArray<LLRegion *> *)regions withEvent:(LLRegionEvent)event {
        // ... do something
    }
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
      // ...Localytics initialization code here...
    
      Localytics.setLocationDelegate(self)
    
      // ... rest of didFinishLaunchingWithOptions ...
    
      return true
    }
    
    func localyticsDidUpdateLocation(_ location: CLLocation) {
      // ... do something
    }
    
    func localyticsDidUpdateMonitoredRegions(_ addedRegions: [LLRegion], remove removedRegions: [LLRegion]) {
      // ... do something
    }
    
    func localyticsDidTriggerRegions(_ regions: [LLRegion], with event: LLRegionEvent) {
      // ... do something
    }
    

Call To Action callbacks

Added in SDK 5.2, call to action callbacks are useful for understanding when Localytics has triggered a deeplink or internal event through a javascript API. All Localytics Call To Action callbacks are called on the main thread.

In your AppDelegate file implement the CallToActionDelegate as follows.

Objective-C Swift

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // ...Localytics initialization code here...

    [Localytics setCallToActionDelegate:self];

    // ... rest of didFinishLaunchingWithOptions ...

    return YES;
}

- (BOOL)localyticsShouldDeeplink:(nonnull NSURL *)url campaign:(nullable LLCampaignBase *)campaign {
    return YES; // return NO to stop Localytics from deeplinking
}
- (void)localyticsDidOptOut:(BOOL)optedOut campaign:(nonnull LLCampaignBase *)campaign {
  // .. do something ..
}
- (void)localyticsDidPrivacyOptOut:(BOOL)privacyOptedOut campaign:(nonnull LLCampaignBase *)campaign {
  // .. do something ..
}
- (BOOL)localyticsShouldPromptForLocationWhenInUsePermissions:(nonnull LLCampaignBase *)campaign {
  return YES; // return NO to stop Localytics from prompting for permissions
}
- (BOOL)localyticsShouldPromptForLocationAlwaysPermissions:(nonnull LLCampaignBase *)campaign {
  return YES; // return NO to stop Localytics from prompting for permissions
}
- (BOOL)localyticsShouldPromptForNotificationPermissions:(nonnull LLCampaignBase *)campaign {
  return YES; // return NO to stop Localytics from prompting for permissions
}
- (BOOL)localyticsShouldDeeplinkToSettings:(LLCampaignBase *)campaign {
  return YES; // return NO to stop Localytics from from navigating to the settings screen
}
- (void)requestAlwaysAuthorization:(nonnull CLLocationManager *)locationManager
  // This callback is to be used by Places customers, and the appropriate location request should be made.
}
- (void)requestWhenInUseAuthorization:(nonnull CLLocationManager *)locationManager
  // This callback is to be used by Places customers, and the appropriate location request should be made.
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
  // ...Localytics initialization code here...

  Localytics.setCallToActionDelegate(self)

  // ... rest of didFinishLaunchingWithOptions ...

  return true
}

func localyticsShouldDeeplink:(_ url: NSURL campaign: LLCampaignBase) -> Bool {
  return true; // return false to stop Localytics from deeplinking
}

func localyticsDidOptOut:(_ optedOut: BOOL campaign: LLCampaignBase) {
  // .. do something ..
}

func localyticsDidPrivacyOptOut:(_ privacyOptedOut: BOOL campaign: LLCampaignBase) {
  // .. do something ..
}

func localyticsShouldPromptForLocationWhenInUsePermissions:(_ campaign: LLCampaignBase) -> Bool {
  return true; // return false to stop Localytics from prompting for permissions
}

func localyticsShouldPromptForLocationAlwaysPermissions:(_ campaign: LLCampaignBase) -> Bool {
  return true; // return false to stop Localytics from prompting for permissions
}

func localyticsShouldPromptForNotificationPermissions:(_ campaign: LLCampaignBase) -> Bool {
  return true; // return false to stop Localytics from prompting for permissions
}

func localyticsShouldDeeplinkToSettings:(_ campaign: LLCampaignBase) -> Bool {
  return true; // return false to stop Localytics from navigating to the settings screen
}

func requestAlwaysAuthorization:(_ locationManager: CLLocationManager *) {
  locationManager.requestAlwaysAuthorization();
}

func requestWhenInUseAuthorization:(_ locationManager: CLLocationManager *) {
  locationManager.requestWhenInUseAuthorization();
}

Tracking location

The Localytics SDK will attach a user's location to a datapoint automatically when a user triggers a geofence (via Places) or whenever you manually set a user's location. Since most apps don't track user geolocation, Localytics also uses an IP-to-location lookup service as a backup measure if no other data is provided. Localytics will always attach the most recently provided location to each datapoint, similar to custom dimensions. Location data is available in the export logs.

You can set the user’s current location in the class that conforms to the CLLocationManagerDelegate as follows.

Objective-C Swift

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
    CLLocationCoordinate2D coordinate = [locations lastObject].coordinate;
    [Localytics setLocation:coordinate];
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    let coordinate = locations.last?.coordinate
    Localytics.setLocation(coordinate!)
}

Custom Places integration

With the standard Places integration the Localytics SDK will automatically monitor geofences and update the device's location using the lowest power setting. However, if you prefer to get location updates yourself and monitor geofences, you can use the APIs described below to notify the Localytics SDK when geofences are entered and exited.

1. Getting geofences to monitor

On each location update get the closest 20 geofences to monitor for the given location as follows. The LLGeofence object contains a CircularRegion, name, and attributes.

Objective-C Swift

NSArray *geofences = [Localytics geofencesToMonitor:coordinate];
let geofences = Localytics.geofencesToMonitor(coordinate)

2. Notifying the SDK of geofence enter and exit triggers

For Places analytics and messaging functionality, notify the SDK when a geofence is entered or exited as follows.

Objective-C Swift

CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:coordinate
                                                             radius:radius
                                                         identifier:identifier];
[Localytics triggerRegion:region withEvent:LLRegionEventEnter atLocation:location];
let region = CLCircularRegion(center: coordinate, radius: radius, identifier: identifier)
Localytics.triggerRegion(region, with: .enter at: location)

3. Notifying the SDK of location updates

Get more accurate location data for your events and sessions by setting the current location of the device by following the steps in Tracking location.

Setting a custom identifier

Once a custom identifier is set, it is attached to each subsequent datapoint, similar to the behavior of custom dimensions. Unlike custom dimensions, however, custom identifiers only end up in export data. They are useful for attaching multiple unique identifiers to a given datapoint for use in joining your Localytics export data with other sources in an enterprise data warehouse. Custom identifiers are not used by the Localytics backend or Dashboard for anything and are just passed through to the export data.

Objective-C Swift

[Localytics setValue:@"Black" forIdentifier:@"Hair Color"];
Localytics.setValue("Black", forIdentifier:"Hair Color")

Creating an explicit App ID

  1. Log in to the Apple Developer console and go to Certificates, Identifiers & Profiles.
  2. Under Identifiers, select App IDs and then click the Add button (+) in the upper-right corner.
  3. Enter a description, select your Team ID as the App ID Prefix, enter an Explicit App ID that exactly matches your Bundle ID, and then click Continue.
  4. Verify the information you entered and then click Register.

Handling data protection

When data protection is enabled at the provisioning profile level or on Localytics' files, some SDK features may not function properly. Specifically, features that make use of background modes, such as Places and using content-available with push messaging, won't work when the device is locked and NSFileProtectionComplete is used or when NSFileProtectionCompleteUntilFirstUserAuthentication is used and the device hasn't been unlocked since the last boot.

We recommend excluding Localytics files from data protection to ensure full analytics and marketing functionality. There are 2 options for encrypting files while excluding Localytics data:

Option 1: Switch from provisioning-profile-level data protection to per-file data protection

Instead of using profile-level protection, use NSFileManager options when creating individual files to turn on complete protection for those specific files. The Localytics SDK will be able to create its files without Data Protection.

Option 2: Keep provisioning-profile-level data protection, but remove the file protection from Localytics’ individual files.

Set NSFileProtectionNone recursively on Localytics folders and files when the SDK is initialized as follows.

  1. In your app delegate header file, ensure that your app delegate conforms to the LLAnalyticsDelegate protocol as follows.

    Objective-C Swift

    @interface YourAppDelegate : UIResponder <UIApplicationDelegate, LLAnalyticsDelegate>
    
    class AppDelegate: UIResponder, UIApplicationDelegate, LLAnalyticsDelegate {
    
  2. Set the LLAnalyticsDelegate after initializing the SDK in didFinishLaunchingWithOptions:.

    Objective-C Swift

    [Localytics autoIntegrate:@"YOUR-LOCALYTICS-APP-KEY"
        withLocalyticsOptions:@{
                                LOCALYTICS_WIFI_UPLOAD_INTERVAL_SECONDS: @5,
                                LOCALYTICS_GREAT_NETWORK_UPLOAD_INTERVAL_SECONDS: @10,
                                LOCALYTICS_DECENT_NETWORK_UPLOAD_INTERVAL_SECONDS: @30,
                                LOCALYTICS_BAD_NETWORK_UPLOAD_INTERVAL_SECONDS: @90
                              }
                launchOptions:launchOptions];
    [Localytics setAnalyticsDelegate:self];
    
    Localytics.autoIntegrate("YOUR-LOCALYTICS-APP-KEY", withLocalyticsOptions:[
                            LOCALYTICS_WIFI_UPLOAD_INTERVAL_SECONDS: 5,
                            LOCALYTICS_GREAT_NETWORK_UPLOAD_INTERVAL_SECONDS: 10,
                            LOCALYTICS_DECENT_NETWORK_UPLOAD_INTERVAL_SECONDS: 30,
                            LOCALYTICS_BAD_NETWORK_UPLOAD_INTERVAL_SECONDS: 90
                          ], launchOptions: launchOptions)
    Localytics.setAnalyticsDelegate(self)
    
  3. Add the excludeProtectionForFilePath: method to your app delegate.

    Objective-C Swift

    - (void)excludeProtectionForFilePath:(NSString *)filePath {
      NSFileManager *manager = [NSFileManager defaultManager];
      if ([manager fileExistsAtPath:filePath]) {
        NSError *error;
        BOOL success = [manager setAttributes:@{NSFileProtectionKey: NSFileProtectionNone}
                                ofItemAtPath:filePath
                                       error:&error];
        NSLog(@"Set attributes for file path: %@. Success = %@. Error: %@", filePath, @(success), error);
      }
    }
    
    func excludeProtectionForFilePath(_ filePath: String) {
        let manager = FileManager.default
        if (manager.fileExists(atPath: filePath)) {
            do {
                try manager.setAttributes([FileAttributeKey.protectionKey: FileProtectionType.none], ofItemAtPath: filePath)
                print("Successfully set attributes for file path: \(filePath).")
            } catch {
                print("Failed to set attributes for file path: \(filePath).")
            }
        }
    }
    
  4. Exlude file protection when a new session is opened.

    Objective-C Swift

    - (void)localyticsSessionDidOpen:(BOOL)isFirst isUpgrade:(BOOL)isUpgrade isResume:(BOOL)isResume {
      if (!isResume) {
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *localyticsDirectory = [[paths firstObject] stringByAppendingPathComponent:@".localytics"];
        NSFileManager *manager = [NSFileManager defaultManager];
        BOOL isDirectory;
        if ([manager fileExistsAtPath:localyticsDirectory isDirectory:&isDirectory]) {
          if (isDirectory) {
            [self excludeProtectionForFilePath:localyticsDirectory];
            NSArray *subpaths = [manager subpathsAtPath:localyticsDirectory];
            for (NSString *path in subpaths) {
              [self excludeProtectionForFilePath:[localyticsDirectory stringByAppendingPathComponent:path]];
            }
          }
        }
      }
    }
    
    func localyticsSessionDidOpen(_ isFirst: Bool, isUpgrade: Bool, isResume: Bool) {
        if !isResume {
            let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
            let localyticsDirectory = (paths.first! as NSString).appendingPathComponent(".localytics")
            let manager = FileManager.default
            var isDirectory : ObjCBool = ObjCBool(false)
            if (manager.fileExists(atPath: localyticsDirectory, isDirectory: &isDirectory)) {
                if isDirectory.boolValue {
                    excludeProtectionForFilePath(localyticsDirectory)
                    let subpaths = manager.subpaths(atPath: localyticsDirectory)
                    for path in subpaths! {
                        excludeProtectionForFilePath((localyticsDirectory as NSString).appendingPathComponent(path))
                    }
                }
            }
        }
    }
    

Sending a test mode push from the Dashboard

After completing push integration you can create a test campaign to send yourself a push message to be sure that everything is properly configured.

  1. Login to the Localytics Dashboard, navigate to Messaging, select your app key from the dropdown at the top of the screen, and click the + button at the top right of the screen.
  2. Name your campaign, e.g. "Push Test", and click Continue to Creatives.
  3. Under Message Body, enter a sample push alert message, e.g. "Push test example message".
  4. Select One Time and Immediately then click Continue and Confirm.
  5. Click Test on Device.
  6. Click Enable Test Mode.
  7. Use one of the options mentioned on the screen to open the test mode URL on your connected device. If the URL doesn't cause your app to open at this point, please ensure that you have completed all steps of Getting Started, especially the test mode step.
  8. Background your app by pressing the home button.
  9. Your test push message will appear on your connected device. If the push does not immediately appear, check that your push certificate is correct, then wait a few minutes and retry.

Handling foreground notifications

This step is only possible if you are using the UserNotification framework introduced in iOS 10 and you are setting a UNUserNotificationCenterDelegate.

In iOS 10 Apple provides a new callback which will only be triggered if the app receives a notification while in the foreground. The below code snippet should be implemented on the application's UNUserNotificationCenterDelegate (typically the AppDelegate) to present the incoming notification.

Objective-C Swift

- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
  completionHandler(UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert);
}
@available(iOS 10.0, *) // if also targeting iOS versions less than 10.0
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    completionHandler([.alert, .sound, .badge])
}

Delaying Session Start in-apps

There are certain scenarios (like when presenting a splash screen) in which In-apps set to trigger on Session Start should be delayed. In order to delay those messages you must implement the messaging callback. An example of how to delay those in-apps would look as follows:

Objective-C Swift

- (BOOL)localyticsShouldDelaySessionStartInAppMessages {
  if ([self isSplashScreenShowing]) {
    return YES;
  }
  return NO;
}
func localyticsShouldDelaySessionStartInAppMessages() -> Bool {
  if self.isSplashScreenShowing() {
    return true
  }
  return false
}

Then in the future when in-apps that are triggered by Session Start are valid, call:

Objective-C Swift

[Localytics triggerInAppMessagesForSessionStart];
        
Localytics.triggerInAppMessagesForSessionStart()
        

Integrating with Firebase

Many developers have started to integrate Firebase alongside Localytics. Unfortunately, the automatic integrations for both platforms can lead to conflicts, particularly around the handling of deeplinks.

To allow Localytics to still handle deeplinks users have two options:

  1. Prevent Firebase from swizzling the methods in your AppDelegate by adding FirebaseAppDelegateProxyEnabled set to NO in your app's info.plist as described in Firebase's documentation
  2. Integrate Localytics manually as described in manual session management.

Integrating with UIWindowScene

The standard Localytics session management approach relies on the Localytics SDK to automatically listen for indications of state change (e.g. foreground / background transitions) in your app and then to track the state change accordingly. Most apps are able to use the standard approach. However, with the introduction of the UIWindowSceneDelegate in iOS 13, the Localytics SDK is now unable to automatically handle these state transitions for your app.

Instead, you will have to add the following code to your app to ensure it's ability to track sessions, and navigate internal deeplinks.

First, implement the UIWindowSceneDelegate on a class in your app (in this example we will append it to the UIApplicationDelegate):

Objective-C Swift

@interface AppDelegate : UIResponder <UIApplicationDelegate, UIWindowSceneDelegate>
class AppDelegate: UIResponder, UIApplicationDelegate, UIWindowSceneDelegate

Next, define a variable to count the number of active foreground scenes in your app. This variable will be necessary to ensure that your app only calls openSession and closeSession when the application is actually foregrounded and backgrounded:

Objective-C Swift

NSInteger openSceneCount = 0;
var openSceneCount:Int = 0

Finally, implement the UIWindowSceneDelegate methods as follows:

Objective-C Swift

- (void)sceneWillResignActive:(UIScene *)scene  API_AVAILABLE(ios(13.0)){
    openSceneCount -= 1;
    if (openSceneCount == 0) {
        [Localytics dismissCurrentInAppMessage];
        [Localytics closeSession];
        [Localytics upload];
    }
}

- (void)sceneDidBecomeActive:(UIScene *)scene API_AVAILABLE(ios(13.0)) {
    if (openSceneCount == 0) {
        [Localytics openSession];
        [Localytics upload];
    }
    openSceneCount += 1;
}

- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts API_AVAILABLE(ios(13.0)) {
  [URLContexts enumerateObjectsUsingBlock:^(UIOpenURLContext * _Nonnull urlContext, BOOL * _Nonnull stop) {
      [Localytics handleTestModeURL:urlContext.URL];
  }];
}
func sceneDidBecomeActive(_ scene: UIScene) {
  if openSceneCount == 0 {
        Localytics.openSession()
        Localytics.upload()
    }
    openSceneCount += 1;
}

func sceneWillResignActive(_ scene: UIScene) {
  openSceneCount -= 1
  if openSceneCount == 0 {
      Localytics.dismissCurrentInAppMessage()
      Localytics.closeSession()
      Localytics.upload()
  }
}

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
  URLContexts.forEach { urlContext
      Localytics.handleTestModeURL(urlContext.URL)
  }
}

Troubleshooting

To gain more insights into whether your Localytics tagging is occurring when expected, you can use the live monitor (requiring SDK 5.5 and later) and make sure that all analytics tags, such as Events and Customer ID, are being sent with the correct values.

You can also enable logging in your app using the code below. When looking at logs, a 202 response code indicates that an upload was successful. Be sure to disable logging in production builds. Localytics log output is required when contacting support.

Objective-C Swift

[Localytics setLoggingEnabled:YES];
Localytics.setLoggingEnabled(true)

Available in SDK 5.0 or higher. You can also save logs to your device in order to test devices on the go and view the logs afterward.

Objective-C Swift

[Localytics redirectLoggingToDisk];
Localytics.redirectLoggingToDisk()

If you wish to opt out of live monitor, you can disable the feature in the app delegate:

Objective-C Swift

[Localytics setOptions:@{@"live_logging_enabled": @(NO)}];
Localytics.options = [
"live_logging_enabled": NSNumber(value: false)
]

Updating the SDK

This section contains guides to help developers using older Localytics SDK versions to update their Localytics integration since a breaking change occurred.

Most Localytics SDK updates do not change existing interfaces and dependencies, though sometimes might add new interfaces. Because these are not breaking changes, you can effortlessly update to the latest Localytics SDK version before each of your app releases.

Localytics will infrequently introduce breaking changes as part of the introduction of new functionality or to lay a better foundation for future feature development.

Migrating to SDK v6

Please remove the following methods if you had been using them:

Objective-C Swift

[Localytics isInAppAdIdParameterEnabled];
[Localytics isInboxAdIdParameterEnabled];
Localytics.isInAppAdIdParameterEnabled()
Localytics.isInboxAdIdParameterEnabled()

Additionally, if you are using the localyticsShouldDeeplink callback from within the LLMessaingDelegate, please make sure to migrate to the same method, with the same return type in the LLCallToActionDelegate.

Updating from v5.4 to v5.5

While developing SDK 5.5, a bug was found which showed that in rare circumstances Localytics Push Received events could be tagged as Localytics Push Opened. As a result, the Localytics SDK now includes a new method to specifically handle push received for apps running iOS10+.

This change is only required for customers using push, and integrated using manual integration. To take advantage of this change, simply change the code in application:didReceiveRemoteNotification:fetchCompletionHandler: (or the variant without the completion handler if that is in your integration).

The original code will look as follows:

Objective-C Swift

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
  [Localytics handleNotification:userInfo];
  completionHandler(UIBackgroundFetchResultNoData);
}
func application(UIApplication, didReceiveRemoteNotification: [AnyHashable : Any], fetchCompletionHandler: (UIBackgroundFetchResult) -> Void) {
  Localytics.handleNotification(userInfo!)
  completionHandler(.noData);
}

This should be updated to the following:

Objective-C Swift

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    if (@available(iOS 10.0, *)) {
        [Localytics handleNotificationReceived:userInfo];
    } else {
        [Localytics handleNotification:userInfo];
    }
    completionHandler(UIBackgroundFetchResultNoData);
}
func application(UIApplication, didReceiveRemoteNotification: [AnyHashable : Any], fetchCompletionHandler: (UIBackgroundFetchResult) -> Void) {
  if (#available(iOS 10.0, *)) {
        Localytics.handleNotificationReceived(userInfo!)
    } else {
        Localytics.handleNotification(userInfo!)
    }
}

Updating from v5.2 to v5.3

Apple now enforces that any app that uses an API which prompts for permissions include info.plist values, or the app could be rejected during App Review. As a result customers the Localytics SDK has removed this functionality from the SDK. Customers who use Places will need to update their integraton to ensure that end users are still prompted. To do so, you will first have to implement the LLCallToActionDelegate:

Objective-C Swift

[Localytics setCallToActionDelegate:self];
Localytics.setCallToActionDelegate(self)

Then implement the location manager specific methods, and request location permissions:

Objective-C Swift

- (void)requestAlwaysAuthorization:(nonnull CLLocationManager *)locationManager
  [locationManager requestAlwaysAuthorization];
}
- (void)requestWhenInUseAuthorization:(nonnull CLLocationManager *)locationManager
  [locationManager requestWhenInUseAuthorization];
}
func requestAlwaysAuthorization:(_ locationManager: CLLocationManager *) {
  locationManager.requestAlwaysAuthorization();
}

func requestWhenInUseAuthorization:(_ locationManager: CLLocationManager *) {
  locationManager.requestWhenInUseAuthorization();
}

Updating from v4 to v5

Follow the steps in each section below to make the appropriate adjustments for updating from v4 to v5. if you're upgrading from v4.0 or below, make sure to check additional sections below.

Integration with Data Flushing

For automatic integration, add the following line to the start of didFinishLaunchingWithOptions:.

Objective-C Swift

[Localytics autoIntegrate:@"YOUR-LOCALYTICS-APP-KEY"
    withLocalyticsOptions:@{
                            LOCALYTICS_WIFI_UPLOAD_INTERVAL_SECONDS: @5,
                            LOCALYTICS_GREAT_NETWORK_UPLOAD_INTERVAL_SECONDS: @10,
                            LOCALYTICS_DECENT_NETWORK_UPLOAD_INTERVAL_SECONDS: @30,
                            LOCALYTICS_BAD_NETWORK_UPLOAD_INTERVAL_SECONDS: @90
                          }
            launchOptions:launchOptions];
Localytics.autoIntegrate("YOUR-LOCALYTICS-APP-KEY", withLocalyticsOptions:[
                        LOCALYTICS_WIFI_UPLOAD_INTERVAL_SECONDS: 5,
                        LOCALYTICS_GREAT_NETWORK_UPLOAD_INTERVAL_SECONDS: 10,
                        LOCALYTICS_DECENT_NETWORK_UPLOAD_INTERVAL_SECONDS: 30,
                        LOCALYTICS_BAD_NETWORK_UPLOAD_INTERVAL_SECONDS: 90
                      ], launchOptions: launchOptions)

If you are using manual integration, you will need to use the new integrate method:

Objective-C Swift

[Localytics integrate:@"YOUR-LOCALYTICS-APP-KEY"
withLocalyticsOptions:@{
                        LOCALYTICS_WIFI_UPLOAD_INTERVAL_SECONDS: @5,
                        LOCALYTICS_GREAT_NETWORK_UPLOAD_INTERVAL_SECONDS: @10,
                        LOCALYTICS_DECENT_NETWORK_UPLOAD_INTERVAL_SECONDS: @30,
                        LOCALYTICS_BAD_NETWORK_UPLOAD_INTERVAL_SECONDS: @90
                        }
        launchOptions:launchOptions];
Localytics.integrate("YOUR-LOCALYTICS-APP-KEY", withLocalyticsOptions: [
    LOCALYTICS_WIFI_UPLOAD_INTERVAL_SECONDS: 5,
    LOCALYTICS_GREAT_NETWORK_UPLOAD_INTERVAL_SECONDS: 10,
    LOCALYTICS_DECENT_NETWORK_UPLOAD_INTERVAL_SECONDS: 30,
    LOCALYTICS_BAD_NETWORK_UPLOAD_INTERVAL_SECONDS: 90
    ])

Localytics attempts to upload user data quickly to our backend to power time sensitive messaging use cases. By default, Localytics will upload data periodically based on the state of a user's network connection. However, you have full flexibility over this behavior. While not recommended, you can change the upload intervals for each type of connection, and even remove this type of behavior entirely and depend on your own Localytics.upload() calls to upload data whenever you wish.

To use the default intervals provided by Localytics, you can pass in nil into localyticsOptions. If you would like to disable scheduled uploads, pass in -1 as the value for all keys.

The available keys for setting upload intervals are:

  • LOCALYTICS_WIFI_UPLOAD_INTERVAL_SECONDS: Defines the interval that will be used in the case of a WiFi connection. Having a WiFi connection will supersede any mobile data connection. Default value is 5 seconds.
  • LOCALYTICS_GREAT_NETWORK_UPLOAD_INTERVAL_SECONDS: Defines the interval that will be used in the case of 4G or LTE connections. Default value is 10 seconds.
  • LOCALYTICS_DECENT_NETWORK_UPLOAD_INTERVAL_SECONDS: Defines the interval that will be used in the case of 3G connection. Default value is 30 seconds.
  • LOCALYTICS_BAD_NETWORK_UPLOAD_INTERVAL_SECONDS: Defines the interval that will be used in the case of 2G or EDGE connections. Default value is 90 seconds.

Other v5 Changes

Please replace the following methods and callbacks:

Objective-C Swift

[Localytics didRegisterUserNotificationSettings:notificationSettings];

[Localytics setInboxCampaignId:campaignId asRead:read];

[Localytics triggerRegion:region withEvent:event];
[Localytics triggerRegions:regions withEvent:event];

- (void)localyticsWillDisplayInAppMessage { ... }
Localytics.didRegisterUserNotificationSettings(notificationSettings)
Localytics.didRegister(notificationSettings)

Localytics.setInboxCampaignId(id, asRead: read)

Localytics.triggerRegion(region, with: event)
Localytics.triggerRegions(regions, with: event)

func localyticsWillDisplayInAppMessage() { ... }

with the following:

Objective-C Swift

[Localytics didRegisterUserNotificationSettings];

[Localytics setInboxCampaign:campaign asRead:read];

[Localytics triggerRegion:region withEvent:event atLocation:location];
[Localytics triggerRegions:regions withEvent:event atLocation:location];

- (nonnull LLInAppConfiguration *)localyticsWillDisplayInAppMessage:(nonnull LLInAppCampaign *)campaign
  withConfiguration:(nonnull LLInAppConfiguration *)configuration { ... }
Localytics.didRegisterUserNotificationSettings()

Localytics.setInboxCampaign(campaign, asRead: read)

Localytics.triggerRegion(region, with: event at: location)
Localytics.triggerRegions(regions, with: event at: location)

func localyticsWillDisplay(inAppMessage campaign: LLInAppCampaign,
      with configuration: LLInAppConfiguration) -> LLInAppConfiguration { ... }

Updating from 4.0 to 4.1+

Follow the steps in each section below to make the appropriate adjustments for updating from 4.0 to 4.1 or later.

Push Notifications Entitlement

Along with the release of iOS 10, Xcode 8 does not automatically copy the aps-environment entitlement from provisioning profiles at build time.

To ensure that push notifications function properly, enable Push Notifications in the Capabilities section of your project's settings as shown in the screenshot below. An APP-NAME.entitlements file will be added to your project.

screenshot of remote notifications BackgroundModes in Info.plist

UserNotifications

iOS 10 introduced the UserNotifications framework that supports the handling, delivery, and modification of local and remote notifications, allows scheduling of local notifications based on time or location, and includes new content types and presentation styles.

Migrating your app to using the UserNotifications framework is required when using Xcode 8. When you migrate, you need to follow the steps below to ensure push tokens are properly registered and push opens are properly tracked.

  1. Update your remote notification registration to the following:

  2. First make sure to import the new UserNotifications framework introduced in iOS10 in whichever file you will ask for push notification permissions.

    Objective-C Swift

    @import UserNotifications;
    
    import UserNotifications
    

    Then request permission to present notifications to the user.

    Objective-C Swift

    if (NSClassFromString(@"UNUserNotificationCenter")) {
      UNAuthorizationOptions options = (UNAuthorizationOptionBadge | UNAuthorizationOptionSound |UNAuthorizationOptionAlert);
      [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:options
                                                                          completionHandler:^(BOOL granted, NSError * _Nullable error) {
                                                                            [Localytics didRequestUserNotificationAuthorizationWithOptions:options
                                                                                                                                   granted:granted];
                                                                          }];
    } else {
      UIUserNotificationType types = (UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound);
      UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
      [application registerUserNotificationSettings:settings];
    }
    
    if #available(iOS 10.0, *), objc_getClass("UNUserNotificationCenter") != nil {
        let options: UNAuthorizationOptions = [.alert, .badge, .sound]
        UNUserNotificationCenter.current().requestAuthorization(options: options) { (granted, error) in
            Localytics.didRequestUserNotificationAuthorization(withOptions: options.rawValue, granted: granted)
        }
    } else {
        let types: UIUserNotificationType = [.alert, .badge, .sound]
        let settings = UIUserNotificationSettings(types: types, categories: nil)
        application.registerUserNotificationSettings(settings)
    }
    
  3. When using the UserNotification framework and using a UNUserNotificationCenterDelegate, you need to notify the Localytics SDK when notifications are opened. Add the following to your userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: delegate method:

    Objective-C Swift

    - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler {
      [Localytics didReceiveNotificationResponseWithUserInfo:response.notification.request.content.userInfo];
      completionHandler();
    }
    
    @available(iOS 10.0, *) // if also targeting iOS versions less than 10.0
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        Localytics.didReceiveNotificationResponse(userInfo: response.notification.request.content.userInfo)
        completionHandler()
    }
    

Updating from v3 to v4

v4 of the Localytics iOS SDK includes several public API changes, support for standard events, and the new Places product.

Follow the steps in each section below to make the appropriate adjustments for updating to v4.

Callbacks

v4 only supports a single callback for the AnalyticsDelegate protocol and the MessagingDelegate protocol. Therefore, the API has changed from add to set. If you are using one of these delegates, confirm that you are only setting this listener in one place in your app.

Replace the following method calls:

[Localytics addAnalyticsDelegate:self];
[Localytics addMessagingDelegate:self];

with the set versions as follows.

[Localytics setAnalyticsDelegate:self];
[Localytics setMessagingDelegate:self];

Push messaging

If you were using alternative session management, the method for handling push receives and opens has changed from handlePushNotificationOpened to handleNotification.

Update the following

[Localytics handlePushNotificationOpened:userInfo];

to

[Localytics handleNotification:userInfo];

Options

If you were previously changing the default session timeout interval, use the new setOptions method.

Update the following

[Localytics setSessionTimeoutInterval:30];

to

[Localytics setOptions:@{@"session_timeout": @30}];

If you were previously suppressing the collection of advertising identifiers (IDFA), use the new setOptions method.

Update the following

[Localytics setCollectAdvertisingIdentifier:NO];

to

[Localytics setOptions:@{@"collect_adid": @NO}];

Updating to 3.5+

Customers who integrated using manual integration and who are using in-app messaging must remove all Localytics calls from the applicationWillResignActive method when upgrading to 3.5.0 so that split screen apps handle in-app messages correctly. If you fail to make this change, split screen apps will dismiss in-app messages when entering or exiting split screen or when changing the split size.

Updating from 3.0 to 3.1+

Use this guide if you have integrated the Localytics library 3.0 into your app and are ready to upgrade to 3.1.

  1. Enable background push. Push reporting will not work well without it.
  2. If you are tracking push, remove application:didReceiveRemoteNotification: if your application uses it and replace it with application:didReceiveRemoteNotification:fetchCompletionHandler:.
    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
      [Localytics handlePushNotificationOpened:userInfo];
      completionHandler(UIBackgroundFetchResultNoData);
    }
    
  3. If your integration is manual, and you use application:didReceiveRemoteNotification:fetchCompletionHandler:, be sure to remove the following code from your application delegate's application:didFinishLaunchingWithOptions: method.
    [[LocalyticsSession shared] handleRemoteNotification:[launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]];
    
  4. If your integration is manual, and you're using in-app messaging, you must remove all Localytics calls from the applicationWillResignActive method so that your app handles in-app messages correctly.

Legacy SDKs

This section contains a reference for some older Localytics SDK version integrations, APIs, and code snippets.

v3 SDK

Callbacks

Analytics callbacks

These analytics callbacks are useful for setting the value of a custom dimension or profile attribute before a session opens, firing an event at the beginning or end of a session, or taking action in your app based on an auto-tagged campaign performance event in the Localytics SDK. All Localytics analytics callbacks are called on an internal background thread.

  1. In your app delegate header file, ensure that your app delegate conforms to the LLAnalyticsDelegate protocol as follows.
    @interface YourAppDelegate : UIResponder <UIApplicationDelegate, LLAnalyticsDelegate>
    
  2. Modify your app delegate as follows to register the Localytics analytics delegate and implement any of the analytics callbacks you require.
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        // ...Localytics initialization code here...
    
        [Localytics addAnalyticsDelegate:self];
    
        // ... rest of didFinishLaunchingWithOptions ...
    
        return YES;
    }
    
    - (void)localyticsSessionWillOpen:(BOOL)isFirst
                            isUpgrade:(BOOL)isUpgrade
                             isResume:(BOOL)isResume
    {
        // ... do something ...
    }
    
    - (void)localyticsSessionDidOpen:(BOOL)isFirst
                           isUpgrade:(BOOL)isUpgrade
                            isResume:(BOOL)isResume
    {
        // ... do something ...
    }
    
    - (void)localyticsDidTagEvent:(NSString *)
                       attributes:(NSDictionary *)attributes
            customerValueIncrease:(NSNumber *)customerValueIncrease
    {
        // ... do something ...
    }
    
    - (void)localyticsSessionWillClose
    {
        // ... do something ...
    }
    
Messaging callbacks

Messaging callbacks are useful for understanding when Localytics will display messaging campaigns. This can help you prevent conflicts with other views in your app, as well as potentially suppress the Localytics display. All Localytics messaging callbacks are called on the main thread.

  1. In your app delegate header file, ensure that your app delegate conforms to the LLMessagingDelegate protocol as follows.
    @interface YourAppDelegate : UIResponder <UIApplicationDelegate, LLMessagingDelegate>
    
  2. Modify your app delegate as follows to register the Localytics messaging delegate and implement any of the analytics callbacks you require.
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        // ...Localytics initialization code here...
    
        [Localytics addMessagingDelegate:self];
    
        // ... rest of didFinishLaunchingWithOptions ...
    
        return YES;
    }
    
    - (void)localyticsWillDisplayInAppMessage
    {
        // ... do something ...
    }
    
    - (void)localyticsDidDisplayInAppMessage
    {
        // ... do something ...
    }
    
    - (void)localyticsWillDismissInAppMessage
    {
        // ... do something ...
    }
    
    - (void)localyticsDidDismissInAppMessage
    {
        // ... do something ...
    }