Prerequisites

Minimal version of iOS

The current version of the FollowAnalytics SDK works with iOS version 9.0 and above.

Before you start to integrate the SDK into your app, you need to register your app and generate its API key on the FollowAnalytics platform, by following the instructions here.

Getting Started

In this section, you will find the basic steps to integrate the SDK into your app:

Once the integration is finished, we highly recommend you test the setup. Then, you can start tagging events and saving attributes in your app.

Installation from CocoaPods

Starting with CocoaPods

The best way to install the SDK is with CocoaPods. This will allow you to easily update the SDK when new versions are released. If you have not installed CocoaPods yet, you may refer to this site, where you can install Cocoapods.

Before you start, make sure to have a Podfile. You could create one by writing pod init in your terminal in your project. To open your Podfile, you could find it in your Workspace, or by writing open -a Xcode Podfile in the terminal.

  1. Add pod 'FollowAnalytics','~> 7.1.0' in the Podfile (see screenshot below)

  2. Run pod repo update from the command line. This will enable CocoaPods to detect the latest available version of FollowAnalytics.

  3. Run pod install

Now FollowAnalytics is successfully installed.

Use the .xcworkspace file

To open your project with Xcode, use the .xcworkspace file generated by CocoaPods.

Installation from Swift Package Manager

Starting with Swift Package Manager

Swift Package Manager is the Apple tool for native implementation of Swift dependencies. To learn more about this you may refer to the official documentation.

  1. Head over to your project in Xcode and click File -> Swift Packages -> Add Package Dependency:

  2. Enter the SDK package repository URL https://github.com/followanalytics/FollowAnalytics-SDK-iOS-SPM and click Next:

  3. Select either Version with the desired rule, or Branch with master to keep up with the latest version and click Next:

  4. Select your app target for the FollowAnalytics framework and select your Notification Service extension target for the FANotificationExtension framework, then click Finish:

  5. After completed those steps the Swift Package dependencies should appear on the left menu of your project:

Manual installation

Cocoapods is the preferred method

Even though you can install the SDK manually, cocoapods is the preferred method, since it will take care of much of the work and ensure your SDK is easily updated.

Setting up FollowAnalytics SDK on Xcode 12.3 and greater

  1. Copy the FollowAnalytics.framework into your project directory. If you want to use it from a different directory you also need to update the Framework Search Paths from the Build Settings with that directory.

  2. Go to your Xcode project’s General settings of your target, drag and drop the FollowAnalytics.framework in the Frameworks, Libraries, and Embedded Content section. Make sure Do Not Embed is set for the FollowAnalytics.framework.

  3. From the Build Settings tap on the + button and select Add User-Defined Setting. Name the setting as FA_SDK_PATH and indicate the finder path where the FollowAnalytics.framework exist. E.g. if you have the framework inside a folder named Frameworks on the project root use $SRCROOT/Frameworks.

  4. Create a new Run Script Phase in your app’s target Build Phases and paste the following snippet in the script text field:

    bash "${FA_SDK_PATH}/FollowAnalytics.framework/embed-frameworks.sh"
    

    This step is required to work around an App Store submission bug when archiving universal binaries and to work around the linker about multi architecrture configurations.

Setting up FollowAnalytics SDK on Xcode 12.2 and below

  1. Copy the FollowAnalytics.framework into your project directory. If you want to use it from a different directory you also need to update the Framework Search Paths from the Build Settings with that directory.

  2. Go to your Xcode project’s General settings of your target, drag and drop the FollowAnalytics.framework in the Frameworks, Libraries, and Embedded Content section.

  3. Create a new Run Script Phase in your app’s target Build Phases and paste the following snippet in the script text field:

    bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/FollowAnalytics.framework/strip-frameworks.sh"
    

    This step is required to work around an App Store submission bug when archiving universal binaries.

Initialize the SDK with your API key

Prerequisites: Generate your API key

If you haven't already, you can generate the API of your app by following the instructions here.

  1. Import the FollowAnalytics framework in your AppDelegate.

  2. Inside your UIApplicationDelegate implementation, create a configuration object and set the required fields

  3. Just below the configuration object, start the SDK

Your code should look like this:

#import <FollowAnalytics/FollowAnalytics.h>

@implementation AppDelegate
- (BOOL)application:(UIApplication*)application
  didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  // ....
  // as early as possible
  FollowAnalyticsConfiguration* configuration = [FollowAnalyticsConfiguration
    configurationWith:^(FollowAnalyticsConfiguration* _Nonnull config) {
      config.apiKey = @"YOUR API KEY";
      #if DEBUG
      config.apiMode = FollowAnalyticsAPIModeDev;
      #else
      config.apiMode = FollowAnalyticsAPIModeProd;
      #endif
  }];
  [FollowAnalytics startWithConfiguration:configuration startupOptions:launchOptions];
  //....
}
// ..
@end
import FollowAnalytics

class AppDelegate: UIResponder, UIApplicationDelegate {
  func application(_ application: UIApplication, didFinishLaunchingWithOptions
    launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // ....
    let configuration = FollowAnalyticsConfiguration.init { (config) in
      config.apiKey = "YOUR API KEY"
      #if DEBUG
      config.apiMode = .dev
      #else
      config.apiMode = .prod
      #endif
    }
    FollowAnalytics.start(with: configuration, startupOptions: launchOptions)
    // ....
  }
}

You can configure the SDK with all the available parameters which you can find here.

Setting up push notifications

To set up push notification, you will need to perform the following steps:

We recommend to use p8 authentication key

We strongly recommend using a p8 authentication key as it is valid for all the apps added to your Apple developer account and doesn't have an expiration date. While a p12 certificate is only valid for a unique app and must be renewed each year to continue sending push notifications to your app.

Generate and import a p12 push certificate

  1. If you don't have a Certificate Signing Request yet, create one by following instructions here.

  2. Go to your Apple developer account in the Certificates, Identifiers & Profiles page.

  3. Click on the Identifiers tab.

  4. If you already have an app Identifier (App ID), click on your App ID, otherwise click on the + button and create a new App ID.

  5. In the list of Capabilities, check the Push Notifications box and click on Save.

  6. Click on the Certificates tab.

  7. Click on the + button to add a new certificate.

  8. Select Apple Push Notification service SSL (Sandbox & Production) and click on Continue.

  9. Select your App ID in the list and click on Continue.

  10. In the file picker, select the previously created Certificate Signing Request from your disk and click on Continue.

  11. Click on Download to retrieve the .cer certificate file.

  12. Double-click the downloaded certificate, this should open the Keychain Access app.

  13. Locate the certificate in Keychain Access under Certificates, right-click on it, select Export Apple Push Services:… and save it as a .p12 file.

  14. Enter a password for your certificate.

  15. Go to the Administration page of your app on the FollowAnalytics platform.

  16. Add the exported .p12 file and click on Proceed.

  17. Enter the certificate password and click on Proceed.

Do not forget to ensure your provisioning profile has the Push Notifications service enabled by expanding it from the list in your developer portal.

Generate and import a p8 authentication key

  1. Go to your Apple developer account in the Certificates, Identifiers & Profiles page.

  2. Click on the Keys tab.

  3. Click on the + button to add a new Key.

  4. Enter a name in the Key Name field.

  5. Check the Apple Push Notification service (APNs)

  6. Click on Continue and then on Register.

  7. Click on Download to retrieve the .p8 file. Note that once downloaded, you will not be able to download the p8 file again.

  8. Get back to the Keys list and click on the Identifiers tab.

  9. If you already have an app Identifier (App ID), click on your App ID, otherwise click on the + button and create a new App ID.

  10. In the list of Capabilities, check the Push Notifications box and and click on Save.

  11. Go to the Administration page of your app on the FollowAnalytics platform.

  12. Add the downloaded .p8 file and click on Proceed.

  13. Enter your Key ID and Issuer Key. Your Key ID can be found from the Keys tab of the Certificates, Identifiers & Profiles page in the Apple developer account. Your Issuer Key is your team ID, it can be found from the Membership page in the Apple developer account.

Do not forget to ensure your provisioning profile has the Push Notifications service enabled by expanding it from the list in your developer portal.

Push notification capabilities

In the Capabilities tab of your Xcode Project:

  1. Enable Push Notifications by setting the switch to ON.

  2. Enable the Remote notifications capability in the Background Modes:

    This will ensure that FollowAnalytics can detect uninstalls and send Silent Push Notifications.

Enable quiet notifications

Since iOS 12, Apple introduced a way to receive non-interruptive notifications before the user grants notification authorization. These are notification delivered quietly (no sound, no banner) and that are only displayed in the Notification Center.

You can allow your app to receive quiet notifications from FollowAnalytics as soon as it's installed on the user's device. To do so, just call requestProvisionalNotificationAuthorization in the didFinishLaunchingWithOptions method of your app delegate, after the SDK initialization call.

+ (void)requestProvisionalNotificationAuthorization;
static func requestProvisionalNotificationAuthorization()

Prompt the user to allow notifications

Later in the user experience, you can ask the user to provide its authorization for notifications. You can do this by calling the requestNotificationAuthorisation method at some point:

+ (void)requestNotificationAuthorization;
static func requestNotificationAuthorisation()

If you don't call and user hasn't allowed notifications, your app will only be able to receive silent notifications.

Enable device registration

Registering devices on iOS

Registering devices is enables users of the FollowAnalytics platform to test their campaigns. Unlike Android, you need to declare the URL scheme on your app on iOS so the users can use this feature.

To allow the user to retrieve the device ID for testing from the platform, declare a URL Scheme in the info tab of your Xcode project using the bundleId of your app as URL Scheme:

  1. Go to the info tab of the project targets.

  2. At the bottom you select the subsection called "URL Types".

  3. Click the + sign at the bottom.

  4. Add the bundle ID of your app in both identifier and URL Schemes fields

URL Scheme

Once configured, your FollowAnalytics device ID can be obtained by opening the following URI: YourURLScheme://followanalytics.com/deviceId, with YourURLScheme as the application bundle ID.

Now, users can add devices to the FollowAnalytics platform without knowing the device ID. From the platform, they will send an email a link that will open the app and allow them to register the device.

Setting up keychain access group

Keychain is a secure storage suitable for short bits of sensitive information and by default, the data saved in one application can not be read in other applications. To share the Device ID across applications of the same publisher it is required to specify a keychain group name. To configure it, you will need to perform the following steps:

Enable keychain sharing

  1. In XCode project settings, select your app target and from the Signing & Capabilities tab click +Capability;
  2. From the popup find and select the Keychain Sharing option;
  3. Back on the capabilities list of the application add the group name to be used, e.g. keychain.group.name.example.

Keychain Group Name

Setup the group name in the SDK

Set the configuration field .keychainGroupName with the respective publisher Team ID and Group Name joined but a ..

To know your Team ID:

  1. Login to your Apple developer account (https://developer.apple.com/account);
  2. Select Membership from the left panel;
  3. Find the Team ID value from the Membership Information list.
FollowAnalyticsConfiguration* configuration = [FollowAnalyticsConfiguration
    configurationWith:^(FollowAnalyticsConfiguration* _Nonnull config) {
      // ...
      // Replacing the YOUR_TEAM_ID with the respective value.
      config.keychainGroupName = @"YOUR_TEAM_ID.keychain.group.name.example";
      // ...
    }];
  let configuration = FollowAnalyticsConfiguration.init { (config) in
    // ...
    // Replacing the YOUR_TEAM_ID with the respective value.
    config.keychainGroupName = "YOUR_TEAM_ID.keychain.group.name.example"
    // ...
  }
  FollowAnalytics.start(with: configuration, startupOptions: launchOptions)

Analytics

To enrich User Analytics on the platform, FollowAnalytics SDK allows you to:

Logs and attributes can also be used to target specific audiences when creating campaigns on the platform. You can find more information about the difference between logs and attributes here.

Our CSM team can help you creating relevant logs in your app. You can also find our recommendations here.

Logs and attributes are ignored by the platform is some conditions

When FollowAnalyticsConfiguration.apiMode is set to Dev, the platform completely ignores data received by the SDK. As a result, data is not included in User Analytics and not usable in Audience targeting, but you can still see them in the Device Observer.

The SDK will automatically set apiMode to Dev in the following conditions:

  • The SDK is started with FollowAnalyticsConfiguration.isVerbose set to true.
  • Your app is running in a simulator.

Be careful to have FollowAnalyticsConfiguration.isVerbose set to false for your app release, otherwise all your app data will be ignored by the platform.

Logging Events and Errors

You can log Events and Errors in your app by calling the following methods:

+ (void)logEvent:(nonnull NSString*)name details:(nullable id)details;
+ (void)logError:(nonnull NSString*)name details:(nullable id)details;
static func logEvent(name: String, details: Any?)
static func logError(name: String, details: Any?)

Those methods print the log description in the console and also print an information about the log validity.

Use name as a unique identifier for your log. Use details to pass specific context to your log.

If details exceeds 60KB it will be considered as invalid. Additionally, to be valid, details must be one of the following:

Passing any invalid details to those methods will:

The following code shows an example of how you can log the viewing and the purchasing of a product:

[FollowAnalytics logEvent:@"Product view" details:@"RF1672"];

NSDictionary *details = @{@"reference": @"RF1672",
                          @"payment_mode" : @"credit_card",
                          @"color" : @"red"};
[FollowAnalytics logEvent:@"Product purchased" details:details];
FollowAnalytics.logEvent("Product view", details: "RF1672")

let details = ["reference": "RF1672",
               "payment_mode": "credit_card",
               "color": "red"]
FollowAnalytics.logEvent("Product purchased", details: details)

Logging Locations

There are two ways for logging locations:

+ (void)logLocationWithLatitude:(double)latitude longitude:(double)longitude;
+ (void)logLocation:(nonnull CLLocation*)location;
static func logLocation(withLatitude: Double, longitude: Double)
static func logLocation(location: CLLocation)

Those methods print the log description in the console and also print an information about the log validity:

One way to use the logLocation method, is to implement the didUpdateLocations method of the CLLocationManagerDelegate to log the last location update. FollowAnalytics SDK will take care of ignoring a location log if this location is too close from the last one:

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
  if let location = locations.last {
    FollowAnalytics.logLocation(location)
  }
}

Last known location logs

If you don't request location updates, and you don't send location logs you can still have location logged thanks to the configuration option lastKnownLocationEnabled.

Enabling this option, by setting FollowAnalyticsConfiguration.lastKnownLocationEnabled to true, will allow the SDK to send a location logs whenever a new session is created and a valid location is available.

The SDK is started by default with FollowAnalyticsConfiguration.lastKnownLocationEnabled set to false.

User Attributes

A user can be defined by setting its identifier. This identifier is a string that you provide to the SDK to uniquely identify a user of your app. It can be an e-mail address, an identifier from your own backend (for instance a primary key), a phone number, or anything else that could allow you to uniquely identify a user.

Attributes (like first name, date of birth...) can be associated to a user if it's defined. Attributes are associated to the device when no user is defined.

When a user is defined, a user profile is created server-side and can be shared across apps.

You can set predefined as well as custom attributes.

Define a user

To define a user, set its user ID:

FollowAnalytics.setUserId("UniqueIdentifier")

To remove a user (for example in case of a sign out), just pass nil to setUserId:

FollowAnalytics.setUserId(nil)

Predefined attributes

To set predefined attributes, you can call the following methods on the userAttributes property provided by the SDK:

- (void)setFirstName:(nullable NSString*)firstName;
- (void)setLastName:(nullable NSString*)lastName;
- (void)setEmail:(nullable NSString*)email;
- (void)setDateOfBirth:(nullable NSDate*)dateOfBirth;
- (void)setGender:(FollowAnalyticsGender)gender;
- (void)setCountry:(nullable NSString*)country;
- (void)setCity:(nullable NSString*)city;
- (void)setRegion:(nullable NSString*)region;
- (void)setProfilePictureUrl:(nullable NSString*)profilePictureUrl;
func setFirstName(firstName : String?)
func setLastName(lastName: String?)
func setEmail(email : String?)
func setDateOfBirth(dateOfBirth : Date?)
func setGender(gender : FollowAnalyticsGender)
func setCountry(country : String?)
func setCity(region : String?)
func setRegion(city : String?)
func setProfilePictureUrl(profilePictureUrl : String?)

For instance, to set user Joe's city to "Paris", you would proceed as follows:

[FollowAnalytics.userAttributes setFirstName:@"Joe"];
[FollowAnalytics.userAttributes setCity:@"Paris"];
FollowAnalytics.userAttributes.setFirstName("Joe")
FollowAnalytics.userAttributes.setCity("Paris")

Custom attributes

In addition to predefined attributes, you can set your own custom attributes.

Always double check your custom attribute types

When a value for an unknown attribute is received by the server, the attribute is declared with the type of that first value.

If you change the type of an attribute in the SDK, values might be refused server-side. Ensure the attribute types match. This could be done by comparing with the ones you have in the Profile Data tab in the Administration page on FollowAnalytics platform.

Set a custom attribute

To set custom attributes, you can call the following methods on the userAttributes property provided by the SDK:

- (void)setInteger:(NSInteger)integerValue forKey:(nonnull NSString*)key;
- (void)setDouble:(double)doubleValue forKey:(nonnull NSString*)key;
- (void)setString:(nonnull NSString*)string forKey:(nonnull NSString*)key;
- (void)setBoolean:(bool)boolean forKey:(nonnull NSString*)key;
- (void)setDate:(nonnull NSDate*)date forKey:(nonnull NSString*)key;
- (void)setDateTime:(nonnull NSDate*)dateTime forKey:(nonnull NSString*)key;
- (void)clear:(nonnull NSString*)key;
- (void)add:(nonnull NSSet<NSString*>*)values toSet:(nonnull NSString*)key;
- (void)remove:(nonnull NSSet<NSString*>*)values toSet:(nonnull NSString*)key;
- (void)clearSet:(nonnull NSString*)key;
func setInteger(integerValue: Int, forKey: String)
func setDouble(doubleValue : Double, forKey: String)
func setString(string: String, forKey: String)
func setBoolean(boolean: Bool, forKey: String)
func setDate(date: Date, forKey: String)
func setDateTime(dateTime: Date, forKey: String)
func clear(key: String)
func add(values: Set<String>, toSet: String)
func remove(values: Set<String>, toSet: String)
func clearSet(key: String)

For example, to set the user's job:

[FollowAnalytics.userAttributes setString:@"Taxi driver" forKey:@"job"];
FollowAnalytics.userAttributes.setString("Taxi driver", forKey:"job")
Delete a custom attribute value

You can delete the value of an attribute using its key. For instance, to delete the user's job:

[FollowAnalytics.userAttributes clear:@"job"];
FollowAnalytics.userAttributes.clear("job")
Set of Attributes

You can add or remove an item to or from a set of attributes.

To add an item to a set:

NSSet *set = [NSSet setWithObjects:@"apple", @"strawberry", @"lemon", nil];
[FollowAnalytics.userAttributes add:set toSet:@"fruits"]; // Adds "apple", "strawberry" and "lemon" to set "fruits".
FollowAnalytics.userAttributes.add(["apple", "strawberry", "lemon"], toSet:"fruits") // Adds "apple", "strawberry" and "lemon" to set "fruits".

To remove an item from a set:

NSSet *set = [NSSet setWithObjects:@"lemon", nil];
[FollowAnalytics.userAttributes remove:set toSet:@"fruits"]; // Removes "lemon" from set "fruits".
FollowAnalytics.userAttributes.remove(["lemon"], toSet:"fruits") // Removes "lemon" from set "fruits".

To clear a set:

[FollowAnalytics.userAttributes clearSet:@"fruits"]; // Removes all items from set "fruits".
FollowAnalytics.userAttributes.clearSet("fruits") // Removes all items from set "fruits".

Opt-in Analytics

FollowAnalytics SDK opt-in analytics state define whether to send logs and attributes to the platform. Opt-in analytics is true by default, meaning logs and attributes will be sent to FollowAnalytics platform.

You can set it to false at SDK initialization by setting the optInAnalyticsDefault parameter to false in the FollowAnalyticsConfiguration object passed to the initialization method. This is only used to set the default opt-in value at the first launch of the app.

This opt-in analytics value can be changed at runtime and it will be persisted for all future launch of the app.

To change opt-in analytics state at runtime, use the setOptInAnalytics method:

+ (void)setOptInAnalytics:(BOOL)state;
static func setOptIn(state: Bool)

This value is persisted in memory and the opt-in analytics state is changed for all subsequent launch of your application, meaning optInAnalyticsDefault is ignored.

To retrieve the current opt-in analytics state, use the getOptInAnalytics method:

+ (BOOL)getOptInAnalytics;
static func Bool getOptInAnalytics()

GDPR

You can record when the user expresses his demand to access or delete his personal data by calling one of the following methods:

[FollowAnalytics.GDPR requestToAccessMyData]
[FollowAnalytics.GDPR requestToDeleteMyData]
FollowAnalytics.gdpr.requestToAccessMyData()
FollowAnalytics.gdpr.requestToDeleteMyData()

The SDK will record all requests and send them to FollowAnalytics servers as soon as network conditions allow it. The SDK remembers pending requests between app restarts.

Campaigns

From the FollowAnalytics platform, you can create:

The SDK take care of displaying campaigns, and provide you ways to customize and interact with messages received from the platform. In this section you will see how to:

Notification Service Extension Framework

Rich notifications are notifications with embedded rich media (images, GIFs, videos). When defining a Push campaign on the platform, you can add a rich media to the notification.

A badge is the number displayed on the icon of the app, indicating to the user that the app has new information. On the platform, you can set by how much the badge is to be increased or decreased at the notification reception on the user's device. This is defined as the badge increment in the Campaign Editor.

The SDK allows your app to receive rich notifications and to manage badges thanks to the FANotificationExtension notification service extension framework. In this section, we will see how to install and use this extension framework by:

Notification Service Extension and iOS version

FANotificationExtension is only available on iOS 10.0 and above. If a user has a device with iOS version under 10.0, he will receive rich notifications but not be able to see the rich media included. Also he will not able to see your app icon's badge change done through the extension framework.

Add the Notification Service Extension target

To make your app able to receive rich notifications & badge incrementation, follow these steps:

  1. In your XCode project, add a new target.



  2. Select Notification Service Extension, give it a name, let's say Notification Service, and confirm. Then when prompted, activate the scheme.



  3. Set the app as the Executable of the extension:

    • Choose the extension
    • Click Edit Scheme



    • Select app in executable field



Install FANotificationExtension from CocoaPods

If you want to use Cocoapods to manage your external dependencies, simply add the following line to your Podfile only in the extension target:

pod 'FANotificationExtension', '1.0.3', :source => 'https://github.com/followanalytics/fa-pod-spec.git'

If you don't have a podfile yet, open a console and do a pod init in your project directory.

Then, you only need to update your Cocoapods dependencies:

pod install

Install FANotificationExtension manually

If you want to manually integrate the .framework, extract the FollowAnalytics SDK archive and copy the FANotificationExtension.framework into your project directory.

On Xcode 12.3 and greater

  1. Go to your Xcode project’s General settings of your extension target, drag and drop the FANotificationExtension.framework in the Frameworks, Libraries, and Embedded Content section. Make sure Do Not Embed is set for the FANotificationExtension.framework.

  2. From the Build Settings tap on the + button and select Add User-Defined Setting. Name the setting as FA_SDK_PATH and indicate the finder path where the FANotificationExtension.framework exist. E.g. if you have the framework inside a folder named Frameworks on the project root use $SRCROOT/Frameworks.

  3. Create a new Run Script Phase in your app’s extension target Build Phases and paste the following snippet in the script text field:

    bash "${FA_SDK_PATH}/FANotificationExtension.framework/embed-frameworks.sh"
    

    This step is required to work around an App Store submission bug when archiving universal binaries and to work around the linker about multi architecrture configurations.

On Xcode 12.2 and below

  1. Select the target of your main app, in Build Phases

    1. Press the + button, and select New Copy File Phase.
    2. Change the destination to Frameworks.
    3. Click in add other.
    4. Add the FANotificationExtension.framework file that you previously copied into your project directory.
    5. Uncheck Copy items if needed box.
  2. Still in the Build Phases create a new Run Script Phase in your app’s extension target Build Phases and paste the following snippet in the script text field:

    bash "PATH_TO_YOUR_PROJECT_DIRECTORY/FANotificationExtension.framework/strip-frameworks.sh"
    

    This step is required to work around an App Store submission bug when archiving universal binaries.

  3. In Build Settings -> Framework Search Paths entry, add the path to where the FANotificationExtension.framework is located (actually your project directory).

Set an App Group for both app and extension targets

Update your provisioning profiles

Make sure to re-download all the provisioning profiles for application and notification service extension targets.

Configuring the SDK to work with the extension

First, make sure you have set the proper App Group for the app target:


The appGroup parameter should be specified in the configuration init of the SDK:

FollowAnalyticsConfiguration* configuration = [FollowAnalyticsConfiguration
    configurationWith:^(FollowAnalyticsConfiguration* _Nonnull config) {
        ...
        config.appGroup = @"group.your.identifier";
    }
];
let configuration = FollowAnalyticsConfiguration.init { (config) in
    ...
    config.appGroup = "group.your.identifier"
}

Implement the Notification Service Extension

First, make sure you have set the proper App Group for the extension target:


Copy and paste the following implementation in the NotificationService file of the extension you created

#import “NotificationService.h”
#import <FANotificationExtension/FANotificationExtension.h>

@interface NotificationService ()

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

@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest*)request
   withContentHandler:(void (^)(UNNotificationContent* _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    [FANotificationService
        getFAContentIfNeededWithRequest:request
                            bestContent:self.bestAttemptContent
                            appGroup:@"group.your.identifier"
                            completion:^(UNMutableNotificationContent* _Nullable newContent) {
                            NSLog(@"NotificationService: %@", request);
                            // Modify the notification content here...

                            self.contentHandler(newContent);
                            }];
}

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

class NotificationService: UNNotificationServiceExtension {

    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest,
                                withContentHandler contentHandler:
                                @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

        if let bestAttemptContent = bestAttemptContent,
        let contentHandler = self.contentHandler {
        // Modify the notification content here...

            FANotificationService.getFAContentIfNeeded(with: request,
                                                        bestContent: bestAttemptContent,
                                                        appGroup: "group.your.identifier") {
                                                        (newContent) in
                                                        if let newC = newContent {
                                                            contentHandler(newC)
                                                        }
            }
        }
    }

    override func serviceExtensionTimeWillExpire() {
        if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }
}

Badge Management

Prerequisites

To use this feature, make sure you have installed and configured FANotificationExtension properly. More information here

Badges are the numbers displayed on the icon of the app, indicating to the user that the app has new information. Our service offers you two ways to update this value. The first one through the campaign editor on our platform and the second one using our SDK methods.

Using our platform, when creating a Push Campaign, it's possible to set a badge increment so that the SDK updates the badge number at push reception.

Using our SDK you can set, increment or get the badge number through the following methods respectively:

[FollowAnalytics.badge setBadge:INTEGER]; // Set the value of the icon badge number
[FollowAnalytics.badge updateBadgeBy:INTEGER]; // Update the value of the icon badge number
[FollowAnalytics.badge badge]; // Get the value of the icon badge number
FollowAnalytics.badge.setBadge(Int) // Set the value of the icon badge number
FollowAnalytics.badge.updateBadgeBy(Int) // Update the value of the icon badge number
FollowAnalytics.badge.badge // Get the value of the icon badge number

Prefer to use FollowAnalytics.badge instead of UIKit

Even though you can update the badge number programmatically using the Apple UIKit applicationIconBadgeNumber, it is required to use our FollowAnalytics.badge public methods to keep incrementing correctly the badge number on future push receptions.

Geofencing triggers and location conditions

On the platform, when creating a campaign with contextual delivery, there are two ways to use locations:

Logging locations has no effect on geofencing and location conditions

Logging locations through the logLocation method will send location events to the FollowAnalytics platform so you can use locations while targeting specific audiences. This has no effect on the Geofencing Events nor Location Condition features of contextual campaigns.

Implement custom behavior on URL opening

From the FollowAnalytics platform, you can add URLs (website links or custom URL schemes) to your Push or In-App campaigns. By default, those links will be opened by the SDK, unless the shouldOpenURL callback is defined on the FollowAnalyticsConfiguration provided at SDK initialization. This callback allows you to implement your own URL opening logic. Use the return value of this callback to allow the SDK to actually open the URL or not:

FollowAnalyticsConfiguration* configuration = [FollowAnalyticsConfiguration
    configurationWith:^(FollowAnalyticsConfiguration* _Nonnull config) {
        config.shouldOpenURL =
            ^BOOL(NSURL* _Nonnull url) {

            // YOUR BEHAVIOR

            // Returning `YES` tells the SDK to open the URL.
            return YES;
        };
    }
];
let configuration = FollowAnalyticsConfiguration.init { (config) in
    config.shouldOpenURL = { (url) in

        // YOUR BEHAVIOR

        // Returning `true` tells the SDK to open the URL.
        return true
    }
}

Universal Links allow your app to be opened to handle links that point to your website. Due to the behavior of the open(_:options:completionHandler:) method, FollowAnalytics SDK will not open those links in your app, but in the browser. See this article from Apple documentation for more details.

In order to open those Universal Links in your app, you must implement the shouldOpenURL callback to forward the url to your application by calling the application(_:continue:restorationHandler:) callback:

config.shouldOpenURL = ^BOOL(NSURL* _Nonnull url) {

    BOOL urlHasBeenHandled = NO;

    // We only want to hanlde Universal Links
    // (i.e. http links that point to your own website).
    if ([url.absoluteString hasPrefix:@"http"]
        && [url.host isEqualToString:@"YOUR_WEBSITE_DOMAIN"]) {

        NSUserActivity* userActivity = 
        [[NSUserActivity alloc] initWithActivityType:NSUserActivityTypeBrowsingWeb];

        userActivity.webpageURL = url;

        urlHasBeenHandled = [self application:UIApplication.sharedApplication
                            continueUserActivity:userActivity
                            restorationHandler:^(NSArray<id<UIUserActivityRestoring>> *
                                                _Nullable restorableObjects) { }];
    }

    // If the url has been handled by the application,
    // we don't want FA SDK to open the url.
    return !urlHasBeenHandled;
};
config.shouldOpenURL = { (url) in

    var urlHasBeenHandled = false

    // We only want to hanlde Universal Links
    // (i.e. http links that point to your own website).
    if url.absoluteString.hasPrefix("http")
        && url.host == "YOUR_WEBSITE_DOMAIN" {

        let userActivity = NSUserActivity(activityType:
            NSUserActivityTypeBrowsingWeb)

        userActivity.webpageURL = url

        urlHasBeenHandled = self.application(UIApplication.shared,
                                                continue: userActivity,
                                                restorationHandler: { _ in })
    }

    // If the url has been handled by the application,
    // we don't want FA SDK to open the url.
    return !urlHasBeenHandled
}

Make sure your implementation of application(_:continue:restorationHandler:) returns false when your app is not able to handle the provided url:

- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:
(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {

    if (![userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
        return NO;
    }

    if (/* Check if userActivity.webpageURL should open screen 1 */) {

        // Present screen 1 ViewController

        return YES;
    }
    else if (/* Check if userActivity.webpageURL should open screen 2 */) {

        // Present screen 2 ViewController

        return YES;
    }

    return NO;
}
func application(_ application: UIApplication,
                 continue userActivity: NSUserActivity,
                 restorationHandler: @escaping ([Any]?) -> Void) -> Bool {

    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb else { return false }

    if /* Check if userActivity.webpageURL should open screen 1 */ {

        // Present screen 1 ViewController

        return true
    }
    else /* Check if userActivity.webpageURL should open screen 2 */ {

        // Present screen 2 ViewController

        return true
    }

    return false
  }

Opt-in Notifications

The FollowAnalytics SDK opt-in notifications status defines whether your app will receive notifications from your Push campaigns. Opt-in notifications is true by default, meaning notifications from your Push campaigns will be received by your app. This status is independent from the iOS notification authorization, which is also needed by your app to display notifications.

Thanks to this opt-in notifications status, you can implement a UI in your app to allow to choose whether to receive notifications, without the need to go to the iOS notification authorization settings. Note that the opt-in notifications status will have no effect if the iOS notification authorization is not allowed, and in this case, your app will not receive notifications from your Push campaigns, whatever the opt-in notifications status. To have the iOS notification authorisation allowed, use the requestNotificationAuthorization method or the requestProvisinalNotificationAuthorization method.

To update your app UI to reflect the current opt-in notifications status, use the getOptInNotifications method:

+ (BOOL)getOptInNotifications;
static func Bool getOptInNotifications()

To update the current opt-in notifications status upon UI change, use the setOptInNotifications method:

+ (void)setOptInNotifications:(BOOL)state;
static func setOptInNotifications(state: Bool)

Handling transistion from opt-out to opt-in notifications

Just calling setOptInNotifications(true) after the user interacts with your app UI to opts-in for notifications could be insufficient if the iOS system notification authorization is not allowed. For this reason, we recommend to implement the following flow after the user opts-in for notifications in your app:

  • Check the return value of FollowAnalytics.getPushNotificationsRegistrationStatus() which is true only if the iOS system notification authorization is allowed AND the SDK opt-in notifications status is true.
  • If false, display a message and button to invite your user to enable the iOS system notification authorization.
  • Call FollowAnalytics.openNotificationSettingsIfNeeded() when the user taps the button. The method will take care of either displaying the notification authorization request alert, or directing to the iOS app settings screen. This will allow the user to enable notifications.

Another possibility is to bypass the first two steps and only implement the last one.

It's possible to set the opt-in notifications status to false at the SDK initialization by setting the optInNotificationsDefault parameter to false in the FollowAnalyticsConfiguration object passed to the initialization method. This is only used to set the default opt-in value at the first launch of the app. Note that calling the setOptInNotifications will persist the opt-in notifications status for all subsequent launch of your application, meaning optInNotificationsDefault will be ignored.

Interactive Notifications

An Interactive Notification is a notification containing custom action buttons that the user can access by revealing the notification:

When tapped, an action button will:

To create a campaign with Interactive Notifications:

NSString* const ActionIdentifierView = @"View";
NSString* const ActionIdentifierDelete = @"Delete";
NSString* const ActionIdentifierRemind = @"Remind me tomorrow";
let ActionIdentifierView = "View"
let ActionIdentifierDelete = "Delete"
let ActionIdentifierRemind = "Remind me tomorrow"
if (@available(iOS 10.0, *)) {

    UNNotificationAction *viewAction = [UNNotificationAction
                                        actionWithIdentifier:ActionIdentifierView
                                        title:NSLocalizedString(ActionIdentifierView, nil)
                                        options:UNNotificationActionOptionForeground];

    UNNotificationAction *deleteAction = [UNNotificationAction
                                           actionWithIdentifier:ActionIdentifierDelete
                                           title:NSLocalizedString(ActionIdentifierDelete, nil)
                                           options:UNNotificationActionOptionDestructive];

    UNNotificationAction *remindAction = [UNNotificationAction
                                         actionWithIdentifier:ActionIdentifierRemind
                                         title:NSLocalizedString(ActionIdentifierRemind, nil)
                                         options:UNNotificationActionOptionForeground];

    // The UNNotificationCategoryOptionCustomDismissAction option allows to
    // call the onNotificationAction callback when the notiifcation is dismissed
    UNNotificationCategory *newContentCategory = [UNNotificationCategory
                                                 categoryWithIdentifier:@"NewContent"
                                                 actions:@[viewAction, deleteAction, remindAction]
                                                 intentIdentifiers:@[]
                                                 options:UNNotificationCategoryOptionCustomDismissAction];

    [[UNUserNotificationCenter currentNotificationCenter]
     setNotificationCategories:[NSSet setWithObject:newContentCategory]];
}
if #available(iOS 10.0, *) {

  let viewAction = UNNotificationAction(identifier: ActionIdentifierView,
                                        title: NSLocalizedString(ActionIdentifierView,
                                                                 comment: ""),
                                        options: .foreground)

  let deleteAction = UNNotificationAction(identifier: ActionIdentifierDelete,
                                          title: NSLocalizedString(ActionIdentifierDelete,
                                                                   comment: ""),
                                          options: .destructive)

  let remindAction = UNNotificationAction(identifier: ActionIdentifierRemind,
                                          title: NSLocalizedString(ActionIdentifierRemind,
                                                                   comment: ""),
                                          options: .foreground)

  // The customDismissAction option allows to call the onNotificationAction
  // callback when the notiifcation is dismissed
  let newContentCategory =
    UNNotificationCategory(identifier: "NewContent",
                           actions: [viewAction, deleteAction, remindAction],
                           intentIdentifiers: [],
                           options: .customDismissAction) 

  UNUserNotificationCenter.current().setNotificationCategories([newContentCategory])
}

Note that the notification action title is a localized version of the actionIdentifier, while the platform displays the actionIdentifier as is in the Push Opened stats.

Implement custom notification sound

When creating a Push Campaign, the editor lets you set a custom sound for the notification:

To configure a custom sound:

Implement custom behavior on notification action

You can implement a custom behavior when the user taps on a notification by implementing the onNotificationAction callback in FollowAnalyticsConfiguration. This allows you to perform specific actions when a notification is tapped:

FollowAnalyticsConfiguration* configuration = [FollowAnalyticsConfiguration
    configurationWith:^(FollowAnalyticsConfiguration* _Nonnull config) {

        config.onNotificationAction = ^(FAActionInfo* _Nonnull actionInfo) {

            if ([actionInfo.identifier isEqualToString:FAActionPushOpen]) {
                // Your behavior for a tap on the notification body

            } else if ([actionInfo.identifier isEqualToString:FAActionPushDismiss]) {

                // Your behavior when the notification is dismissed (only for Interactive Notification)

            } else if ([actionInfo.identifier isEqualToString:ActionIdentifierView]) {

                // Your behavior for a tap on the "View" action button (see example in Interaction Notification section)

            } else if ([actionInfo.identifier isEqualToString:ActionIdentifierDelete]) {

                // Your behavior for a tap on the "Delete" action button (see example in Interaction Notification section)

            } else if ([actionInfo.identifier isEqualToString:ActionIdentifierRemind]) {

                // Your behavior for a tap on the "Remind me tomorrow" action button (see example in Interaction Notification section)

            }
        };
    }
];
let configuration = FollowAnalyticsConfiguration.init { (config) in
    config.onNotificationAction = { (actionInfo) in

        switch (actionInfo.identifier) {

        case FAActionPushOpen:
          // Your behavior for a tap on the notification body

        case FAActionPushDismiss:
          // Your behavior when the notification is dismissed (only for Interactive Notification)

        case ActionIdentifierView:
          // Your behavior for a tap on the "View" action button (see example in Interaction Notification section)

        case ActionIdentifierDelete:
          // Your behavior for a tap on the "Delete" action button (see example in Interaction Notification section)

        case ActionIdentifierRemind:
          // Your behavior for a tap on the "Remind me later" action button (see example in Interaction Notification section)

        default:
          // Do nothing
        }
    }
}

Add background fetch capability

For your app to be woken-up to execute your implementation of onNotificationAction when your app is in background, you must activate the Background Fetch capability in the Background Modes. This will be the case when the user tap a category notification button that does not open the app.

The SDK passes some Push campaign information to your app through this callback thanks to the actionInfo argument:

Implement custom behavior on Push reception

You can implement a custom behavior when the device receives a message from a Push campaign that contains custom parameters (i.e. at least one key/value pair). This can be used to fetch data from your server if a particular custom parameter is defined in the Push message. To do so, implement the onPushMessageReceived callback in FollowAnalyticsConfiguration:

FollowAnalyticsConfiguration* configuration = [FollowAnalyticsConfiguration
    configurationWith:^(FollowAnalyticsConfiguration* _Nonnull config) {
        config.onPushMessageReceived = ^(FAMessage * _Nonnull message){
            // YOUR BEHAVIOR
        };
    }
];
let configuration = FollowAnalyticsConfiguration.init { (config) in
    config.onPushMessageReceived = { (message) in
        // YOUR BEHAVIOR
    }
}

Add background fetch capability

For your app to be woken-up to execute your implementation of onPushMessageReceived when your app is in background, you must activate the Background Fetch capability in the Background Modes. Note that your code implemented in in this callback must not take more that 30s to execute. Otherwise the callback execution will be stopped by iOS. As a consequence, when using this callback to fetch data, make sure data size not too big compared to the data connection speed of your users.

Implement only synchronous calls

The SDK executes your onPushMessageReceived code in a background thread and expects that the whole callback implementation is synchronous. Any asynchronous call is not guaranteed to finish before the end of the callback execution and then will be terminated at the time the callback execution is finished. Make sure that your implementation follows those rules, to ensure proper runtime execution:

  • All statements are synchronous calls.
  • No call to any API that should be called on the main thread.

The SDK passes some Push campaign information to your app through this callback thanks to the message argument:

Implement custom behavior on native InApp action

The SDK has the following behavior when the user closes an native InApp (i.e. Native Alert or Evaluation Booster) by tapping on one of its native buttons:

You can define a URL on a Native Alert button in the platform's campaign editor. For Evaluation Booster campaign, a URL is automatically set on the Rate button of the Positive answer dialog.

Additionally to the SDK behavior, you can define a custom behavior when the user closes a native InApp by implementing the onInAppNativeAction callback in FollowAnalyticsConfiguration. This allows your app to follow a specific logic for each closing button:

FollowAnalyticsConfiguration* configuration = [FollowAnalyticsConfiguration
    configurationWith:^(FollowAnalyticsConfiguration* _Nonnull config) {

        config.onInAppNativeAction = ^(FAActionInfo* _Nonnull actionInfo) {
            // YOUR BEHAVIOR
        };
    }
];
let configuration = FollowAnalyticsConfiguration.init { (config) in
    config.onInAppNativeAction = { (actionInfo) in
        // YOUR BEHAVIOR
    }
}

The SDK passes some native InApp campaign information to your app through this callback thanks to the actionInfo argument:

Controlling In-App campaigns

To pause and resume campaigns, add the following methods in you code at the location you wish the pause and resume to take effect:

[FollowAnalytics.inApp pauseCampaignDisplay]
[FollowAnalytics.inApp resumeCampaignDisplay]
FollowAnalytics.inApp.pauseCampaignDisplay()
FollowAnalytics.inApp.resumeCampaignDisplay()

Create safe spaces for you in-app messages

Rather than pause everywhere you have an important screen or process, you can pause right at the initialization of the SDK and resume in the areas you think it is safe for in-app campaigns to be displayed.

Archiving messages

FollowAnalytics SDK allows you to store campaigns's messages received by your app. This makes them available for custom usage, like building an inbox feature. All campaigns displayed by a device can be archived locally and accessed from the developer's code, formatted as a FAMessage object. In order to configure your campaign storage, you have to set the following FollowAnalyticsConfiguration properties at SDK initialization:

@property(nonatomic, readonly, assign) BOOL archivePushMessages;
@property(nonatomic, readonly, assign) BOOL archiveInAppMessages;
open var archivePushMessages: Bool { get }
open var archiveInAppMessages: Bool { get }

In FollowAnalyticsInApp and FollowAnalyticsPush protocols you will find all necessary methods to manage all FAMessage objects that are archived. They are implemented by FollowAnalytics.inApp and FollowAnalytics.push and must be called from the main thread.

- (nonnull NSArray<FAMessage*>*)getAll;
- (nullable FAMessage*)get:(nonnull NSString*)identifier;
- (void)display:(nonnull NSString*)identifier;
- (void)markAsRead:(nonnull NSArray<NSString*>*)identifiers;
- (void)markAsUnread:(nonnull NSArray<NSString*>*)identifiers;
- (void)deleteIdentifiers:(nonnull NSArray<NSString*>*)identifiers;
func getAll() -> [FAMessage]
func get(identifier : String) -> FAMessage
func display(identifier : String)
func markAsRead(identifiers : [String])
func markAsUnread(identifiers : [String])
func deleteIdentifiers(identifiers : [String])

Here is a description of some of the FAMessage attributes:

Property Type Description
isRead boolean Indicates if the message has been read not not. False at message reception. Only modifiable by the InBoxManager.
isPush boolean true if message comes from a Push campaign, false if it comes from a In-App campaign.
isInApp boolean true if message comes from a In-App campaign, false if it comes from a Push campaign.
isSilent boolean true if message comes from a Push campaign with the "Silent" option enabled (silent push), false otherwise.
isNotificationDismissed boolean true if message comes from a non-silent Push Campaign and the related notification is dismissed, false otherwise.
identifier string FollowAnalytics message identifier.
date date If message comes from a Push campaign, this is the date at which the message is received on the device. If message comes from an In-App campaign, this is the date a which the message is displayed on the device.
notificationId string If message comes from a Push campaign, this is the UNNotificationRequest unique identifier (iOS >= 10.0), otherwise it's nil.
type string
title string Text provided in the platform's "Title" field of the Push campaign editor.
subtitle string Text provided in the platform's "Subtitle" field of the Push campaign editor.
body string Text provided in the platform's "Content" field of the Push campaign editor.
contentUrl string Url provided in the platform's "URL" field of In-App Custom Web Page campaign editor. If the message comes from a Push campaign, this is the url of the Rich Media.
layout string
openingUrl string Url provided in the platform's "App Link" field of the Push campaign editor.
parameters dictionary Key/Value pairs provided in the Push campaign editor.
category string Interactive notification category.

Re-display archived InApp messages

From the SDK interface, you have access to the method displayInApp() on the FAMessage object that will re-display an In-App campaign. Calling this method from a Push message it will not trigger any re-display and will return FALSE. Also, this re-display doesn't have impact on the current recurring contextual In-Apps and will not trigger any log on the platform.

Web Views

If your app contains web views, you can use FollowAnalytics SDK from within your HTML/JS code. This is possible thanks to the FAWKWebViewJSBridge.

Using FAWKWebViewJSBridge

  1. Adopt the WKUIDelegate protocol in your object of choice, typically a UIViewController.

    @interface ViewController <WKUIDelegate>
    
    class ViewController: UIViewController, WKUIDelegate {
    
  2. At the initialization of the hosting view controller, keep a strong reference to a FAWKWebViewJSBridge instance:

    @property(nonatomic, nullable) FAWKWebViewJSBridge* webViewJSBridge;
    // ...
    self.webViewJSBridge = [[FAWKWebViewJSBridge alloc] init];
    
    var webViewJSBridge: FAWKWebViewJSBridge?
    // ...
    self.webViewJSBridge = FAWKWebViewJSBridge()
    
  3. Add the FAWKWebViewJSBridge instance to the WKWebViewConfiguration instance, pass this configuration to the WKWebView and set your UIViewController as the web view delegate:

    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
    [self.webViewJSBridge addToConfiguration:configuration];
    WKWebView* webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];
    webView.UIDelegate = self;
    
    let configuration = WKWebViewConfiguration()
    self.webViewJSBridge?.add(to: configuration)
    let webView = WKWebView.init(frame: self.view.frame, configuration: configuration)
    webView.uiDelegate = self
    
  4. Implement the following delegate method:

    - (void)webView:(WKWebView *)webView
                  runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt
                  defaultText:(nullable NSString *)defaultText
                  initiatedByFrame:(WKFrameInfo *)frame
                  completionHandler:(void (^)(NSString * _Nullable result))completionHandler {
      if([prompt hasPrefix:@"FollowAnalytics"]) {
        [self.webViewJSBridge webView:webView
              runJavaScriptTextInputPanelWithPrompt:prompt
              defaultText:defaultText
              initiatedByFrame:frame
              completionHandler:completionHandler];
          return;
      }
    }
    
    func webView(_ webView: WKWebView,
                 runJavaScriptTextInputPanelWithPrompt prompt: String,
                 defaultText: String?,
                 initiatedByFrame frame: WKFrameInfo,
                 completionHandler: @escaping (String?) -> Void) {
      if prompt.hasPrefix("FollowAnalytics") {
        self.webView(webView,
                     runJavaScriptTextInputPanelWithPrompt: prompt,
                     defaultText: defaultText,
                     initiatedByFrame: frame,
                     completionHandler: completionHandler
        return
      }
    }
    

FollowAnalytics SDK JS interface

Here are the SDK methods you can call from the JavaScript code:

Disabling Swizzling

By default, the SDK swizzles methods from your UIApplicationDelegate, your UNUserNotificationCenterDelegate, and from your UISceneDelegate if your project is configured with it. Along with the swizzled UISceneDelegate methods our SDK observes the scene lifecycle notifications. This is an automatic process that allows the SDK to receive the necessary system calls seamlessly and behave accordingly without additional configurations and calls from the developer.

But in some situations, you could have the need to disable this feature. For example due to a legacy framework for some incompatibility with the swizzled methods.

In these cases we allow the developer to disable the Swizzling. To that, include the swizzlingEnabled options in the configuration as follows:

FollowAnalyticsConfiguration* configuration = [FollowAnalyticsConfiguration
  configurationWith:^(FollowAnalyticsConfiguration * _Nonnull config) {
    config.swizzlingEnabled = NO;
}];
let configuration = FollowAnalyticsConfiguration.init { (config) in
     config.swizzlingEnabled = false
  }

Required Methods

By disabling the swizzling the SDK requires the developer to forward the following methods without exception. This is mandatory to ensure the SDK works as expected. Not doing so may cause unexpected side-effects.

The required UIApplicationDelegate methods are:

  1. didRegisterForRemoteNotificationsWithDeviceToken:

    Forwarding this method the SDK will be able to send the device token to the server for Push Notifications delivery.

    - (void) application:(UIApplication*) application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*) deviceToken {
      [FollowAnalytics didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
    }
    
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
      FollowAnalytics.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
    }
    
  2. applicationDidEnterBackground:

    Forward this method if your projects don't implement the UISceneDelegate protocol. Forwarding this method the SDK will be able to calculate session duration, handle app badge number and detect campaign triggers.

    - (void) applicationDidEnterBackground:(UIApplication*_Nonnull) application {
      [FollowAnalytics applicationDidEnterBackground:application];
    }
    
    func applicationDidEnterBackground(_ application: UIApplication) {
      FollowAnalytics.applicationDidEnterBackground(application)
    }
    
  3. applicationWillEnterForeground:

    Forward this method if your projects don't implement the UISceneDelegate protocol. Forwarding this method the SDK will be able to synchronize campaigns, detect campaign triggers and present queued triggered campaigns.

    - (void) applicationWillEnterForeground:(UIApplication*_Nonnull) application {
      [FollowAnalytics applicationWillEnterForeground:application];
    }
    
    func applicationWillEnterForeground(_ application: UIApplication) {
      FollowAnalytics.applicationWillEnterForeground(application)
    }
    
  4. applicationDidBecomeActive:

    Forward this method if your projects don't implement the UISceneDelegate protocol. Forwarding this method the SDK will be able to calculate sessions durations and detect campaign triggers.

    - (void) applicationDidBecomeActive:(UIApplication*_Nonnull) application {
      [FollowAnalytics applicationDidBecomeActive:application];
    }
    
    func applicationDidBecomeActive(_ application: UIApplication) {
      FollowAnalytics.applicationDidBecomeActive(application)
    }
    
  5. applicationWillTerminate:

    Forwarding this method the SDK will be able to calculate sessions durations and handle app badge number.

    - (void) applicationWillTerminate:(UIApplication*_Nonnull) application {
      [FollowAnalytics applicationWillTerminate:application];
    }
    
    func applicationWillTerminate(_ application: UIApplication) {
      FollowAnalytics.applicationWillTerminate(application)
    }
    
  6. applicationDidReceiveMemoryWarning:

    Forwarding this method the SDK will be able to send statistics information.

    - (void) applicationDidReceiveMemoryWarning:(UIApplication*_Nonnull) application {
      [FollowAnalytics applicationDidReceiveMemoryWarning:application];
    }
    
    func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
      FollowAnalytics.applicationDidReceiveMemoryWarning(application)
    }
    
  7. didReceiveRemoteNotification:

    Forwarding this method the SDK will be able to handle Push Notifications and In-App campaigns.

    - (void) application:(UIApplication*_Nonnull) application didReceiveRemoteNotification:(NSDictionary*_Nullable) userInfo {
      [FollowAnalytics application:application didReceiveRemoteNotification:userInfo];
    }
    
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
      FollowAnalytics.application(application, didReceiveRemoteNotification: userInfo)
    }
    
  8. handleActionWithIdentifierForRemoteNotification:

    Forwarding this method the SDK will be able to handle Push Notifications and In-App campaigns.

    - (void) application:(UIApplication*_Nonnull) application handleActionWithIdentifier:(NSString*_Nullable) identifier forRemoteNotification:(NSDictionary*_Nonnull) userInfo completionHandler:(void (^_Nullable)(void)) completionHandler {
      [FollowAnalytics application:application handleActionWithIdentifier:identifier forRemoteNotification:userInfo completionHandler:completionHandler];
    }
    
    func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, forRemoteNotification userInfo: [AnyHashable: Any], completionHandler: @escaping () -> Void) {
      FollowAnalytics.application(application, handleActionWithIdentifier: identifier, forRemoteNotification: userInfo, completionHandler: completionHandler)
    }
    
  9. handleActionWithIdentifierForLocalNotification:

    Forwarding this method the SDK will be able to handle Notifications and In-App campaigns.

    - (void) application:(UIApplication*_Nonnull) application handleActionWithIdentifier:(NSString*_Nullable) identifier forLocalNotification:(UILocalNotification*_Nonnull) notification completionHandler:(void (^_Nullable)(void)) completionHandler {
      [FollowAnalytics application:application handleActionWithIdentifier:identifier forLocalNotification:notification completionHandler:completionHandler];
    }
    
    func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, for notification: UILocalNotification, completionHandler: @escaping () -> Void) {
      FollowAnalytics.application(application, handleActionWithIdentifier: identifier, for: notification, completionHandler: completionHandler)
    }
    
  10. handleOpenURL:

    If your application support iOS version less than 9.0.

    Forwarding this method will allow the application to present an alert providing the current device status if the URL is related to this SDK.

    - (BOOL) application:(UIApplication*_Nonnull) application handleOpenURL:(NSURL*_Nonnull) url {
      return [FollowAnalytics application:application handleOpenURL:url];
    }
    
    func application(_ application: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey: Any] = [:]) -> Bool {
      return FollowAnalytics.application(application, open: url, options: options)
    }
    
  11. openURLSourceApplication:

    If your application support iOS version less than 9.0.

    Forwarding this method will allow the application to present an alert providing the current device status if the URL is related to this SDK.

    - (BOOL) application:(UIApplication*_Nonnull) application openURL:(NSURL*_Nonnull) url sourceApplication:(NSString*_Nullable) sourceApplication annotation:(id _Nonnull) annotation {
      return [FollowAnalytics application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
    }
    
    func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
      return FollowAnalytics.application(application, open: url, sourceApplication: sourceApplication, annotation: annotation)
    }
    
  12. openURLOptions:

    Forwarding this method will allow the application to present an alert providing the current device status if the URL is related to this SDK.

    - (BOOL) application:(UIApplication*_Nonnull) application handleOpenURL:(NSURL*_Nonnull) url {
      return [FollowAnalytics application:application handleOpenURL:url];
    }
    
    func application(_ application: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey: Any] = [:]) -> Bool {
      return FollowAnalytics.application(application, open: url, options: options)
    }
    
  13. didReceiveLocalNotification:

    Forwarding this method the SDK will be able to handle In-App campaigns.

    - (void) application:(UIApplication*_Nonnull) application didReceiveLocalNotification:(UILocalNotification*_Nonnull) notification {
      [FollowAnalytics application:application didReceiveLocalNotification:notification];
    }
    
    func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
      FollowAnalytics.application(application, didReceive: notification)
    }
    
  14. performFetchWithCompletionHandler:

    Forwarding this method the SDK will be able to close sessions and fetch contents.

    - (void) application:(UIApplication*_Nonnull) application performFetchWithCompletionHandler:(void (^_Nullable)(UIBackgroundFetchResult)) completionHandler {
      [FollowAnalytics application:application performFetchWithCompletionHandler:completionHandler];
    }
    
    func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
      FollowAnalytics.application(application, performFetchWithCompletionHandler: completionHandler)
    }
    
  15. didReceiveRemoteNotificationFetchCompletionHandler:

    Forwarding this method the SDK will be able to synchronize campaigns, handle the badge icon and detect campaign triggers.

    - (void) application:(UIApplication*_Nonnull) application didReceiveRemoteNotification:(NSDictionary*_Nonnull) userInfo fetchCompletionHandler:(void (^_Nonnull)(UIBackgroundFetchResult)) completionHandler {
      [FollowAnalytics application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
    }
    
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
      FollowAnalytics.application(application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler)
    }
    
  16. handleEventsForBackgroundURLSession:

    Forwarding this method the SDK will be able to synchronize campaigns, handle the badge icon and detect campaign triggers.

    - (void) application:(UIApplication*_Nonnull) application handleEventsForBackgroundURLSession:(NSString*_Nonnull) identifier completionHandler:(void (^_Nullable)(void)) completionHandler {
      [FollowAnalytics application:application handleEventsForBackgroundURLSession:identifier completionHandler:completionHandler];
    }
    
    func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
      FollowAnalytics.application(application, handleEventsForBackgroundURLSession: identifier, completionHandler: completionHandler)
    }
    

The required UNUserNotificationCenterDelegate methods are:

  1. didReceiveNotificationResponse:

    Forwarding this method the SDK will be able to handle Push Notifications and In-App campaigns.

    - (void) userNotificationCenter:(UNUserNotificationCenter*) center didReceiveNotificationResponse:(UNNotificationResponse*) response withCompletionHandler:(void (^)(void)) completionHandler {
      [FollowAnalytics userNotificationCenter:center didReceiveNotificationResponse:response withCompletionHandler:completionHandler];
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
      FollowAnalytics.userNotificationCenter(center, didReceive: response, withCompletionHandler: completionHandler)
    }
    
  2. willPresentNotification:

    Forwarding this method the SDK will be able to handle Push Notifications and In-App campaigns.

    - (void) userNotificationCenter:(UNUserNotificationCenter*) center willPresentNotification:(UNNotification*) notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions)) completionHandler {
      [FollowAnalytics userNotificationCenter:center willPresentNotification:notification withCompletionHandler:completionHandler];
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
      FollowAnalytics.userNotificationCenter(center, willPresent: notification, withCompletionHandler: completionHandler)
    }
    

And if your project implements the UISceneDelegate the required methods to forward are:

  1. willConnectToSession::

    + (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
      [FollowAnalytics scene:scene willConnectToSession:session options:connectionOptions];
    }
    
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
      FollowAnalytics.scene(scene, willConnectTo: session, options: connectionOptions)
    }
    
  2. sceneDidEnterBackground::

    - (void)sceneDidEnterBackground:(UIScene *)scene {
      [FollowAnalytics sceneDidEnterBackground:scene];  
    }
    
    func sceneDidEnterBackground(_ scene: UIScene) {
      FollowAnalytics.sceneDidEnterBackground(scene)  
    }
    
  3. sceneWillEnterForeground::

    - (void)sceneWillEnterForeground:(UIScene *)scene {
      [FollowAnalytics sceneWillEnterForeground:scene];  
    }
    
    func sceneWillEnterForeground(_ scene: UIScene) {
      FollowAnalytics.sceneWillEnterForeground(scene)  
    }
    
  4. sceneDidBecomeActive::

    - (void)sceneDidBecomeActive:(UIScene *)scene {
      [FollowAnalytics sceneDidBecomeActive:scene];  
    }
    
    func sceneDidBecomeActive(_ scene: UIScene) {
      FollowAnalytics.sceneDidBecomeActive(scene)  
    }
    
  5. openURLContexts::

    - (void) scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
      [FollowAnalytics scene:scene openURLContexts:URLContexts];
    }
    
    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
      FollowAnalytics.scene(scene, openURLContexts: URLContexts)
    }
    

Configuration

To customize the SDK behavior, you must define your desired SDK parameters by setting the related properties values on the FollowAnalyticsConfiguration object passed to the SDK initialization method:

Parameter Type Default Value Description
apiKey string - Your app api key. You can find it on the FollowAnalytics platform in the Administration section.
appGroup string - An app group identifier to link the extension target to the app.
isVerbose boolean false true to see internal logs made by the SDK in the console. Note that setting isVerbose to true will automatically set apiMode to .dev. So make sure to set isVerbose to false for production build. More information in the Analytics section.
apiMode enum .prod .dev or .prod for Swift, FollowAnalyticsAPIModeDev or FollowAnalyticsAPIModeProd for Objective-C. In Dev apiMode, logs, attributes and crashes sent by the SDK will be ignored by the platform, but you still can see them in the Device Observer. More information in the Analytics section.
optInAnalyticsDefault boolean true true to make the user opt-in analytics by default. More information in the Opt-in Analytics section.
optInNotificationsDefault boolean true true to make the user opt-in notifications by default. More information in the Opt-in Notifications section.
crashReportingEnabled boolean true true to enable FollowAnalytics crash reporting.
archivePushMessages boolean false true to archive Push campaigns messages. More information in the Archiving messages section.
archiveInAppMessages boolean false true to archive In-App campaigns messages. More information in the Archiving messages section.
maxBackgroundTimeWithinSession int 120 To determine the lifetime of a session when in background (between 15 and 3600).
onPushMessageReceived callback - Called when device receives a message from a Push campaign that contains custom parameters (i.e. at least one key/value pair).
onNotificationAction callback - Called when user taps on a notification.
onInAppNativeAction callback - Called when user taps on a custom button of a Native Alert In-App campaign.
shouldOpenURL callback true Called when user taps on a URL (website links or custom URL schemes) from a Push or In-App campaigns. This callback returns a boolean value indicating to the SDK if it should call open(_:options:completionHandler:) or not. More information in the Implement custom behavior on URL opening section.
swizzlingEnabled boolean true false to disable the Swizzling process. More information in the Disabling swizzling section.
lastKnownLocationEnabled boolean false true to enable the logging of the last known location when the session starts.

Updating from older versions

Updating from 6.9 to 7.0

Updating from 6.8 to 6.9

#import <FollowAnalytics/FollowAnalytics.h>

@implementation AppDelegate
- (BOOL)application:(UIApplication*)application
  didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  // ....
  FollowAnalyticsConfiguration* configuration = [FollowAnalyticsConfiguration
    configurationWith:^(FollowAnalyticsConfiguration* _Nonnull config) {
      config.apiKey = @"YOUR API KEY";
      #if DEBUG
      config.apiMode = FollowAnalyticsAPIModeDev;
      #else
      config.apiMode = FollowAnalyticsAPIModeProd;
      #endif
  }];
  [FollowAnalytics startWithConfiguration:configuration startupOptions:launchOptions];
  //....
}
// ..
@end
import FollowAnalytics

class AppDelegate: UIResponder, UIApplicationDelegate {
  func application(_ application: UIApplication, didFinishLaunchingWithOptions
    launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // ....
    let configuration = FollowAnalyticsConfiguration.init { (config) in
      config.apiKey = "YOUR API KEY"
      #if DEBUG
      config.apiMode = .dev
      #else
      config.apiMode = .prod
      #endif
    }
    FollowAnalytics.start(with: configuration, startupOptions: launchOptions)
    // ....
  }
}

Updating from 6.4 to 6.5

Updating from 6.4.0 to 6.4.1

Updating from 6.3 to 6.4

Updating from 6.2 to 6.3

If your app implements onIncomingDeepLink and onOpenURL callbacks:

Updating from 6.1 to 6.2

Updating to 6.0

Updating from 5.3 to 5.4

Updating from 5.2 to 5.3

Updating from 5.0 to 5.1

Updating from 4 to 5.0.0

To use version 5.0.0+ of the iOS SDK:

Updating from 3.* to 4.*

The migration is seamless. However, please refer to the section around user attributes to learn how to feed user profiles using the SDK.

Updating from 2.* to 3.0.0

To use version 3.0.0+ of the iOS SDK:

These new parameters are provided only when running on iOS 8 and later. They are the interactive notification parameters that you would want to get to know which action was performed on a FollowAnalytics notification.

Nothing else changes. Please refer to the rest of the documentation to discover what the newer versions of the SDK now allow.