You can get started with using Taplytics on iOS or tvOS in minutes.
Just follow the below steps below.


Installation

First, you'll want to install our SDK inside your XCode project.


Install using CocoaPods

  • Create a Podfile using pod init and add Taplytics to your podfile:
# For iOS:
  pod 'Taplytics'
# For tvOS:
  pod 'Taplytics_tvOS'
  • Run pod install
  • Open your project's .xcworkspace file

That's it! You can then call Taplytics in your didFinishLaunchingWithOptions method in your AppDelegate. Proceed to Initialize Taplytics here.


Manual Installation

For iOS

CoreTelephony.framework
SystemConfiguration.framework
JavaScriptCore.framework

For tvOS

SystemConfiguration.framework
JavaScriptCore.framework

That's it! You can then call Taplytics in your didFinishLaunchingWithOptions method in your AppDelegate. Proceed to Initialize Taplytics here.


Install with Segment

The Taplytics SDK can also be installed via Segment. You can find install instructions here.


Install with mParticle

Check the Taplytics integration for the mParticle Apple SDK docs here


Initialization

Initialize the SDK by adding a line of code with your SDK key in your UIApplicationDelegate. Make sure to call startTaplyticsAPIKey: before making self.window the key window, such as: [self.window makeKeyAndVisible] or [self.window makeKeyWindow].

// iOS
 #import <Taplytics/Taplytics.h>
 // tvOS
 #import <Taplytics_tvOS/Taplytics.h>
 ...
 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
     ...
     [Taplytics startTaplyticsAPIKey:@"SDK_KEY"];
     ...
     // make sure you call startTaplytics before any makeKeyWindow calls:
     // [self.window makeKeyAndVisible];
     // [self.window makeKeyWindow];
     ...
     return YES;
 }
// For iOS
import Taplytics
// For tvOS
import Taplytics_tvOS
...
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    ...
    Taplytics.startAPIKey("SDK_KEY")
    ...
    // make sure you call startTaplytics before any makeKeyWindow calls:
    // self.window.makeKeyAndVisible()
    // self.window.makeKeyWindow()
    ...
    return true
}

Taplytics can also be initialized with a couple additional parameters. Users can define start options to change how the SDK functions and what data it retains, more info on that here. There is also an option for users to define a callback for when Taplytics finishes loading.

[Taplytics startTaplyticsAPIKey:@"SDK_KEY" options:[NSDictionary dictionary] callback:^(BOOL loaded) {
    // Finished loading
}]
Taplytics.startAPIKey("SDK_KEY", options: [
  TaplyticsOptionDisable: [TaplyticsDisableTrackLocation]
]) { (loaded) in
    // Finished loading Taplytics
}

Important Notes

It is highly recommended to initialize Taplytics in the AppDelegate’s didFinishedLaunchingWithOptions for several reasons.

  1. Taplytics uses the information on how your app was launched to support some of our core features. ie. View construction/data for the Visual Editor
  2. Taplytics should also be started as early as possible to allow time for Taplytics to fetch our config and present the correct experiment and feature data before the first activity starts.
  3. Taplytics uses the application start as a signal for when the App has been activated. We use this data to accurately track sessions and activity.

Advanced Pairing

Note: Advanced pairing is only supported by iOS. tvOS does not support advanced pairing!

You can implement Advanced Pairing, which will allow you to pair your device to Taplytics via a link sent by email or text. Advanced Pairing is an easy way for your team to pair any build of your app to Taplytics.

  • First ensure that application:openURL:options: method is implemented in your UIApplicationDelegate
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
    return NO;
}
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
    return false
}
1548
  • Add Taplytics URL Type in XCode's Build Info panel, with Identifier: com.taplytics, add your Taplytics URL Scheme from above.
1036

Setting User Attributes

It's possible to segment your users based on custom user attributes. To set this up, you need to make either the user_id or email unique to identify the user across multiple devices. Below is a list of standard custom fields you can send to Taplytics. There is also a customData field that allows you to send any custom data you have as a flat NSDictionary with NSJSONSerialization accepted values.

The possible fields are:

ParameterType
emailNSString
user_idNSString
firstNameNSString
lastNameNSString
nameNSString
ageNSNumber
genderNSString
customDataNSDictionary

For example:

[Taplytics setUserAttributes: @{
    @"user_id": @"testUser",
    @"name": @"Test User",
    @"email": @"[email protected]",
    @"gender": @"female",
    @"age": @25,
    @"avatarUrl": @"https://pbs.twimg.com/profile_images/497895869270618112/1zbNvWlD.png",
    @"customData": @{
        @"paidSubscriber": @YES,
        @"purchases": @3,
        @"totalRevenue": @42.42
    }
}];
Taplytics.setUserAttributes([
    "user_id": "testUser",
    "name": "Test User",
    "email": "[email protected]",
    "gender": "female",
    "age": 25,
    "avatarUrl": "https://pbs.twimg.com/profile_images/497895869270618112/1zbNvWlD.png",
    "customData": [
        "paidSubscriber": true,
        "purchases": 3,
        "totalRevenue": 42.42
    ]
])

NOTE: The customData field is limited to 50kb of JSON string data.


User Attributes on First Launch

User Attributes set before startTaplyticsAPIKey: is called, will be used for experiment segmentation on the first session of your app. Any attributes that are set after startTaplyticsAPIKey: is called will not be used for experiment segmentation until the next session of your app.

// These custom data values will be used for segmentation on the first session of the app.
[Taplytics setUserAttributes:@{@"customData": @{@"paid_user": @YES}}];

[Taplytics startTaplyticsAPIKey:@"SDK_KEY"];

// These custom data values will only take effect on the second session of the app.
[Taplytics setUserAttributes:@{@"customData": @{@"demo_account": @NO}}];
Taplytics.setUserAttributes(["customData": ["paid_user": true]])

Taplytics.startAPIKey("SDK_KEY")

Taplytics.setUserAttributes(["customData": ["demo_account": false]])

You can also use setUserAttributes with a block that can notify you when the call to the server has completed.

[Taplytics setUserAttributes:@{@"customData": @{@"demo_account": @NO}} withCallback:^(void) {
    // Finished setting user attributes!
}];
Taplytics.setUserAttributes(["customData": ["paid_user": true]]) {
    // Finished setting user attributes!        
}

Retrieving Session Info

Taplytics also offers a method to retrieve select information of what you know about a session at a given time. This method returns the user's Taplytics identifier (appUser_id) and current session id (session_id)

[Taplytics getSessionInfo:^(NSDictionary * _Nullable sessionInfo) {
    // Use the NSDictionary of sessionInfo
}];
Taplytics.getSessionInfo { (sessionInfo) in
    // Use the NSDictionary of sessionInfo
}

Resetting Users

Once a user logs out of your app, their User Attributes are no longer valid. You can reset their data by calling resetUser:, make sure you do not set any new user attributes until you receive the callback.

[Taplytics resetUser:^{
  // Finished User Reset
}];
Taplytics.resetUser {
  // Finished User Reset
}

User Opt-In / Opt-Out

Using the User Opt-In / Opt-Out APIs allows you to simplify the process to get user consent for analytics tracking and experimentation. Calling optOutUserTracking will disable all Taplytics analytics tracking and experiments, and calling optInUserTracking will re-enable all Taplytics analytics tracking and experiments. You can retrieve the current status using: hasUserOptedOutTracking.

[Taplytics optOutUserTracking];

[Taplytics optInUserTracking];

BOOL hasOptedOut = [Taplytics hasUserOptedOutTracking];
Taplytics.optOutUserTracking()
        
Taplytics.optInUserTracking()

let hasOptedOut = Taplytics.hasUserOptedOutTracking()

Tracking Events


Automatic Tracking

The following events and general information are automatically tracked by Taplytics and will appear on your dashboard.

  • App Active/Background/Terminated
  • View Appeared/Disappeared
  • Time on View
  • Location information and Authorized/Denied events
  • Social Sign-in Authorized/Denied
  • Social Share Authorized/Denied
  • Photo Library Authorized/Denied
  • Push Notification Authorized/Denied (iOS only)
  • Push Notification Seen/Opened (iOS only)

External Analytics Sources

If you already have Analytics events instrumented with another Analytics source Taplytics will also automatically pull in the events from the following sources:


Disabling Automatic Tracking

You can disable automatic tracking for any of the below constants by adding them to the disable array.

ConstantDescription
TaplyticsDisableTrackLocationlocation tracking via GPS
TaplyticsDisableTrackSocialSignInsocial sign in access alert tracking
TaplyticsDisableTrackiAdInstallationapp install from iAd
TaplyticsDisableTrackPhotoLibraryAccessphoto library access alert tracking
TaplyticsDisableSourceGoogleAnalyticsGoogle Analytics event tracking
TaplyticsDisableSourceFlurryFlurry Analytics event tracking
TaplyticsDisableSourceMixpanelMixpanel Analytics event tracking
TaplyticsDisableSourceIntercomIntercom event tracking
TaplyticsDisableSourceParseParse Analytics event tracking
TaplyticsDisableSourceApsalarApsalar Analytics event tracking
TaplyticsDisableSourceAdobeAdobe Analytics event tracking
TaplyticsDisableSourceLocalyticsLocalytics Analytics event tracking
TaplyticsDisableSourceCrashlyticsCrashlytics Answers Analytics event tracking
TaplyticsDisableSourceKISSMetricsKISSMetrics Analytics event tracking
TaplyticsDisableSourceHeapHeap Analytics event tracking
TaplyticsDisableSourceFirebaseFirebase Analytics event tracking
TaplyticsDisableUITableViewSWUITableView tracking
TaplyticsDisableUICollectionViewSWUICollectionView tracking
TaplyticsDisableUIPageViewSWUIPageView tracking
TaplyticsDisableUIActionSheetSWUIActionSheet tracking
TaplyticsDisableUIAlertViewSWUIAlertView tracking
TaplyticsDisableUITextFieldViewSWUITextField tracking

For example:

[Taplytics startTaplyticsAPIKey:@"SDK_KEY" options:@{
    TaplyticsOptionDisable: @[TaplyticsDisableTrackLocation]
}];
Taplytics.startAPIKey("SDK_KEY", options: [
    TaplyticsOptionDisable: [TaplyticsDisableTrackLocation]
])

Background Session Time

Taplytics automatically tracks sessions for you. The Taplytics SDK keeps track of the last activity timestamp in your app (app activity is considered a view change, button click, or Taplytics event logged), and when your app returns from background if the time since last activity is greater then 10 minutes we create a new session for you. If you would like the session background time something other than 10 minutes you can set it as a start option:

[Taplytics startTaplyticsAPIKey:@"SDK_KEY" options:@{TaplyticsOptionSessionBackgroundTime: @10}];
Taplytics.startAPIKey("SDK_KEY", options: [TaplyticsOptionSessionBackgroundTime: 10])

Taplytics Events

You can also track your own custom Analytics events to Taplytics using the logEvent: methods:

[Taplytics logEvent:@"CustomEvent"];

// With NSNumber value and metaData
[Taplytics logEvent:@"CustomEvent" value:@42 metaData:@{@"userSubscribed":@YES}];

// Log Revenue
[Taplytics logRevenue:@"Purchase" value:@10.25 metaData:@{@"Item":@"blueSweater"}];
Taplytics.logEvent("CustomEvent")

// With Number value and metaData
Taplytics.logEvent("CustomEvent", value: 42, metaData: ["userSubscribed": true])

// Log Revenue
Taplytics.logRevenue("Purchase", revenue: 10.15, metaData: ["Item": "blueSweater"])

NOTE: the event metaData is limited to 50kb of JSON string data.


Firebase Events

Firebase has a limit of 500 distinct event names per project. As a way around this, you can pass in the start option TaplyticsOptionCustomFirebaseFormat and send the event names in the parameters instead with any additional metadata.

Note: If passing the event name in the parameters, you must track the event name using the key eventName.

// Normal way of logging events
[FIRAnalytics logEventWithName:@"CustomEvent" parameters:@{@"userSubscribed":@YES}];
[FIRAnalytics logEventWithName:@"AnotherCustomEvent" parameters:@{@"userSubscribed":@YES}];

// With start option enabled
[FIRAnalytics logEventWithName:@"tlEvents" parameters:@{@"eventName":@"CustomEvent", @"userSubscribed":@YES}];
[FIRAnalytics logEventWithName:@"tlEvents" parameters:@{@"eventName":@"AnotherCustomEvent", @"userSubscribed":@YES}];
// Normal way of logging events
Analytics.logEvent("CustomEvent", parameters: ["userSubscribed": true])
Analytics.logEvent("AnotherCustomEvent", parameters: ["userSubscribed": true])

// With start option enabled
Analytics.logEvent("tlEvents", parameters: ["eventName": "CustomEvent", "userSubscribed": true])
Analytics.logEvent("tlEvents", parameters: ["eventName": "AnotherCustomEvent", "userSubscribed": true])

NOTE: event metaData is limited to 50kb of JSON string data.


Experiment Information Postbacks

If you choose to, the Taplytics SDK can also send the running Experiment/Variation information to a supported Analytics source. Check out our docs for details.


Start Options

Start options allow you to control how certain SDK features function, and enable or disable features.

Start OptionTypeDescription
TaplyticsOptionDelayLoadNSNumberControls the maximum time the SDK will show your launch image for. More details.
TaplyticsOptionShowLaunchImageNSNumber(Boolean)Controls whether or not the SDK will hold the launch image during DelayLoad. Note: Disabling this feature may result in users seeing visual changes, or code variables using default values for first session
TaplyticsOptionLaunchImageTypeNSStringIf you are using a xib as a launch image set the value as @"xib". This will stop the caught exception that occurs for xib based launch images.
TaplyticsOptionShowShakeMenuNSNumber(Boolean)To disable the shake menu from showing up in development mode, set to @NO
TaplyticsOptionTestExperimentsNSNumber(Boolean)To test specific experiments, pass in the experiment name/variation name as the key/values of a NSDictionary. More details.
TaplyticsOptionDisableBordersNSNumber(Boolean)To disable all Taplytics borders in development mode, set to @YES
TaplyticsOptionAsyncLoadingNSNumber(Boolean)Forces loading of taplytics properties from disk as async task, breaks some synchronous variable behaviour, see section below for details.
TaplyticsOptionLoggingNSNumber(Boolean)Provides more verbose logging from Taplytics to help with debugging.
TaplyticsOptionCustomFirebaseFormatNSNumber(Boolean)Allows you to pass Firebase event names in the parameters as a workaround Firebase's 500 event limit. More details

Example:

[Taplytics startTaplyticsAPIKey:@"SDK_KEY" options:@{
    TaplyticsOptionDelayLoad: @6,
    TaplyticsOptionLaunchImageType: @"xib",
    TaplyticsOptionShowShakeMenu: @NO,
    TaplyticsOptionDisableBorders: @YES,
    TaplyticsOptionTestExperiments: @{
          @"Experiment 1": @"Variation 1",
           @"Experiment 2": @"baseline"
    }
}];
Taplytics.startAPIKey("SDK_KEY", options: [
    TaplyticsOptionDelayLoad: 6,
    TaplyticsOptionLaunchImageType: "xib",
    TaplyticsOptionShowShakeMenu: false,
    TaplyticsOptionDisableBorders: true,
    TaplyticsOptionTestExperiments: [
        "Experiment 1": "Variation 1",
        "Experiment 2": "baseline"
    ]
])

Async Loading

Enabling the start option TaplyticsOptionAsyncLoading will make the initial loading of Taplytics properties from disk run on an async thread. However, this will break the behaviour of synchronous variables where they used the value loaded from disk, with TaplyticsOptionAsyncLoading enabled and synchronous variables are initialized before properties are loaded from disk they will use the default value. To ensure properties are loaded when initializing synchronous variables use the callback provided with startTaplyticsAPIKey or [Taplytics newSessionCallback] or [Taplytics propertiesLoadedCallback:].

Notes:

  • [Taplytics newSessionCallback] and [Taplytics propertiesLoadedCallback:] must be placed after startTaplytics.
  • Please use [Taplytics newSessionCallback] on versions 3.0.0 and above, [Taplytics propertiesLoadedCallback:] has been deprecated as of version 3.0.0.

Existing behaviour example:

[Taplytics startTaplyticsAPIKey:@"SDK_KEY"];

// Existing behaviour would have loaded value from disk and the variable's value would be loaded from disk.
self.var = [TaplyticsVar taplyticsSyncVarWithName:@"syncVar" defaultValue:@1];
NSLog(@"Variable Value: %@", _var.value);
Taplytics.startAPIKey("SDK_KEY")

// Existing behaviour would have loaded value from disk and the variable's value would be loaded from disk.
self.tlVar = TaplyticsVar.sync(name: "syncVar", defaultValue: 1 as NSNumber)
print("Variable Value: \(tlVar.value)")

Async Loading example:

[Taplytics startTaplyticsAPIKey:@"SDK_KEY" options:@{TaplyticsOptionAsyncLoading: @YES}];

// Here var.value would not be loaded from disk and would have the default value of @1
self.var = [TaplyticsVar taplyticsSyncVarWithName:@"syncVar" defaultValue:@1];
NSLog(@"Variable Value: %@", _var.value);

// NEW on version 3.0.0 and above
// Using the newSessionCallback:
__weak AppDelegate* weakSelf = self;
[Taplytics newSessionCallback:^(BOOL loaded) {
    // Now the variable will have the correct value loaded from disk/server
    weakSelf.var = [TaplyticsVar taplyticsSyncVarWithName:@"syncVar" defaultValue:@1];
    NSLog(@"Variable Value: %@", weakSelf.var.value);
}];

// Deprecated on version 3.0.0
// Using the propertiesLoadedCallback:
__weak AppDelegate* weakSelf = self;
[Taplytics propertiesLoadedCallback:^(BOOL loaded) {
    // Now the variable will have the correct value loaded from disk/server
    weakSelf.var = [TaplyticsVar taplyticsSyncVarWithName:@"syncVar" defaultValue:@1];
    NSLog(@"Variable Value: %@", weakSelf.var.value);
}];
Taplytics.startAPIKey("SDK_KEY", options: [TaplyticsOptionAsyncLoading: true])

// Here var.value would not be loaded from disk and would have the default value of @1
self.tlVar = TaplyticsVar.sync(name: "syncVar", defaultValue: 1 as NSNumber)
print("Variable Value: \(tlVar.value)")

// NEW on version 3.0.0 and above
// Using the newSessionCallback:
Taplytics.newSessionCallback { (loaded) in
    guard loaded, let label = self.label else {
        return
    }

    self.tlVar = TaplyticsVar.sync(name: "syncVar", defaultValue: 1 as NSNumber)
    if let tlVariable = self.tlVar, let stringValue = tlVariable.value as? String {
        print("Variable Value: \(stringValue)")
    }
}

// Deprecated on version 3.0.0
// Using the propertiesLoadedCallback:
Taplytics.propertiesLoadedCallback { (loaded) in
    guard loaded, let label = self.label else {
        return
    }

    self.tlVar = TaplyticsVar.sync(name: "syncVar", defaultValue: 1 as NSNumber)
    if let tlVariable = self.tlVar, let stringValue = tlVariable.value as? String {
        print("Variable Value: \(stringValue)")
    }
}

Creating experiments is easy using Taplytics. You can either use our visual editor or create code-based experiments. You can find documentation on how to do this below.


Dynamic Variables & Code Blocks

To see and modify these variables or blocks on the dashboard, the app must be launched and this code containing the variable or block must be navigated to a least once.

The code below is used to send the information of the variable or block to Taplytics, so it will appear on the dashboard. Taplytics variables are values in your app that are controlled by experiments. Changing the values can update the content or functionality of your app. Variables are reusable between experiments and operate in one of two modes: synchronous or asynchronous.


Synchronous

Synchronous variables are guaranteed to have the same value for the entire session and will have that value immediately after construction.

Due to the synchronous nature of the variable, if it is used before the experiments have been loaded from Taplytics servers (for example on the first launch of your app), it's value will be the default value rather than the value set for that experiment. This could taint the results of the experiment. In order to prevent this you can ensure that the experiments are loaded before using the variable. This can be done using the propertiesLoadedCallback: method, as an example:

// New as of version 3.0.0
[Taplytics newSessionCallback:^(BOOL loaded) {
    [self loadTLVariables];
}];

// Deprecated as of version 3.0.0
[Taplytics propertiesLoadedCallback:^(BOOL loaded) {
    [self loadTLVariables];
}];
// New as of version 3.0.0
Taplytics.newSessionCallback { (loaded) in
    loadTLVariables()
}

// Deprecated as of version 3.0.0
Taplytics.propertiesLoadedCallback { (loaded) in
    loadTLVariables()
}

Note that this must be done only after startTaplytics.

Synchronous variables take two parameters in its constructor:

  1. Variable name (String)
  2. Default Value

The type of the variable is defined by the type of the Default Value and can be a JSON serializable NSDictionary, NSString, NSNumber or a Boolean casted to a NSNumber.

We do support arrays within JSON code variables, however, it has to be a top level array where it would resemble something like this {"a" : [1,2,3]}.

Here's an example, using a variable of type String, using its value to get the value of the variable:

TaplyticsVar* stringVar = [TaplyticsVar taplyticsSyncVarWithName:@"stringVar" defaultValue:@"string"];
NSString* string = (NSString*)stringVar.value;
let stringVar = TaplyticsVar.sync(name: "stringVar", defaultValue: "string" as NSString)
let string = stringVar.value as? String

Using a casted Boolean to a NSNumber:

TaplyticsVar* boolVar = [TaplyticsVar taplyticsSyncVarWithName:@"boolVar" defaultValue:@(YES)];
BOOL boolean = [(NSNumber*)boolVar.value boolValue];
let boolVar = TaplyticsVar.sync(name: "boolVar", defaultValue: true as NSNumber)
let boolean = boolVar.value as? Bool

Asynchronous

Asynchronous variables take care of insuring that the experiments have been loaded before returning a value. This removes any danger of tainting the results of your experiment with bad data. What comes with the insurance of using the correct value is the possibility that the value will not be set immediately. If the variable is constructed before the experiments are loaded, you won't have the correct value until the experiments have finished loading. If the experiments fail to load, then you will be given the default value, as specified in the variables constructor.

Asynchronous variables take three parameters in its constructor:

  1. Variable name (String)
  2. Default Value
  3. TLVarBlock

Just as for synchronous variables the type of the variable is defined by the type of the default value, and can be a NSString, NSNumber or a Boolean casted to a NSNumber.

For example, using a variable of type NSNumber:

// In your Interface create a strong reference to the variable
@property (nonatomic, strong) TaplyticsVar* tlVar;

// Using the variable in your code:
self.tlVar = [TaplyticsVar taplyticsVarWithName:@"numVar" defaultValue:@(1) updatedBlock:^(NSObject *value) {
    if (value) {
        NSNumber* num = (NSNumber*)value;
    }
}];
self.tlVar = TaplyticsVar.async(name: "numVar", defaultValue: 1 as NSNumber) { (updatedValue) in
    guard let value = updatedValue as? NSNumber else {
        return
    }
    // use value
}

When the variable's value has been updated, the updated block will be called with that updated value. Note that we only store a weak reference to your variables, for the updated block to work correctly you will need to store a strong reference to the variable object.

Note: Default values for dynamic variables cannot be NULL. NULL values may cause default to trigger in all scenarios


Testing Dynamic Variables

When testing dynamic variables in live update mode you can change the values on the fly via the Taplytics interface and you can switch variations with the shake menu on the device.

Important Note: When testing synchronous dynamic variables you must initialize the variable again to see the new value, as there are no callbacks which occur when the variable is updated with a new value.

This can be achieved by using a new session callback (version 3.0.0 and above) or properties loaded callback (under version 3.0.0). Here is an example for updating a label:

__weak id weakSelf = self;
[Taplytics newSessionCallback:^(BOOL loaded) {
    TaplyticsVar* var = [TaplyticsVar taplyticsSyncVarWithName:@"stringVar" defaultValue:@"defaultValue"];
    if (weakSelf && weakSelf.label) {
        weakSelf.label.text = var.value;
    }
}];
Taplytics.newSessionCallback { (loaded) in
    guard loaded, let label = self.label else {
        return
    }

    let stringVar = TaplyticsVar.sync(name: "stringVar", defaultValue: "string" as NSString)
    if let stringValue = stringVar.value as? String {
        label.text = stringValue
    }
}

Testing Specific Experiments

To test/QA specific experiment and variation combinations use the TaplyticsOptionTestExperiments start option with a NSDictionary containing keys of the experiment names, and values of variation names (or baseline).

[Taplytics startTaplyticsAPIKey:@"SDK_KEY" options:@{
    TaplyticsOptionTestExperiments: @{
        @"Experiment 1": @"Variation 1",
        @"Experiment 2": @"baseline"
    }
}];
Taplytics.startAPIKey("SDK_KEY", options: [
    TaplyticsOptionTestExperiments: [
        "Experiment 1": "Variation 1",
        "Experiment 2": "baseline"
    ]
])

Visual Editing

You don't have to do anything else! You can use the Taplytics dashboard to make all your visual changes. See the docs on visual editing here.

Note: Only available using our iOS framework, not available on tvOS.


List Running Experiments

If you would like to see which variations and experiments are running on a given device, there exists a getRunningExperimentsAndVariations() function which provides a callback with the current experiments and their running variation. An example:

[Taplytics getRunningExperimentsAndVariations:^(NSDictionary *experimentsAndVariations) {
    // For example experimentsAndVariations will contain:
    // @{
    //     @"Experiment 1": @"baseline",
    //     @"Experiment 2": @"Variation 1"
    // };
}];
Taplytics.getRunningExperimentsAndVariations { (experimentsAndVariations) in
    // For example experimentsAndVariations will contain:
    // [
    //    "Experiment 1": "baseline",
    //    "Experiment 2": "Variation 1",
    // ]
}

Note: The block can return asynchronously once Taplytics properties have loaded. The block will return a NSDictionary with experiment names as the key value, and variation names as the value.


Delay Load for First-view Experiments

On the first launch of your app, the Taplytics SDK will show your iOS launch image up to a maximum 4 seconds while it downloads properties from Taplytics servers. This delay will enable you to run experiments in the first view of your app.

If you would like to disable showing the launch image:

[Taplytics startTaplyticsAPIKey:@"SDK_KEY" options:@{TaplyticsOptionDelayLoad:@0}];
Taplytics.startAPIKey("SDK_KEY", options: [TaplyticsOptionDelayLoad: 0])

Or increase the maximum wait time to 10 seconds:

[Taplytics startTaplyticsAPIKey:@"SDK_KEY" options:@{TaplyticsOptionDelayLoad:@10}];
Taplytics.startAPIKey("SDK_KEY", options: [TaplyticsOptionDelayLoad: 10])

Feature Flags

Taplytics feature flags operate in synchronous mode. Synchronous feature flags are guaranteed to have the same value for the entire session and will have that value immediately after construction.

if ([Taplytics featureFlagEnabled:@"featureFlagKey"]) {
    // Put feature code here, or launch feature from here
}
if (Taplytics.featureFlagEnabled("featureFlagKey")) {
    // Put feature code here, or launch feature from here
}

Due to the synchronous nature of feature flags, if it is used before the feature flags have been loaded from Taplytics servers (for example on the first launch of your app), it will default to as if the feature flag is not present. In order to prevent this you can ensure that the feature flags are loaded before using the feature flag. This can be done using the propertiesLoadedCallback: method, as an example:

// New as of version 3.0.0
[Taplytics newSessionCallback:^(BOOL loaded) {
    if ([Taplytics featureFlagEnabled:@"featureFlagKey"]) {
        // Put feature code here, or launch feature from here
    }
}];

// Deprecated as of version 3.0.0
[Taplytics propertiesLoadedCallback:^(BOOL loaded) {
    if ([Taplytics featureFlagEnabled:@"featureFlagKey"]) {
        // Put feature code here, or launch feature from here
    }
}];
// New as of version 3.0.0
Taplytics.newSessionCallback { (loaded) in
    if (Taplytics.featureFlagEnabled("featureFlagKey")) {
        // Put feature code here, or launch feature from here
    }
}

// Deprecated as of version 3.0.0
Taplytics.propertiesLoadedCallback { (loaded) in
    if (Taplytics.featureFlagEnabled("featureFlagKey")) {
        // Put feature code here, or launch feature from here
    }
}

List Running Feature Flags

If you would like to see which feature flags are running on a given device, there exists a getRunningFeatureFlags() function which provides a callback with the current experiments and their running variation. An example:

[Taplytics getRunningFeatureFlags:^(NSDictionary *featureFlags) {
    // For example featureFlags will contain:
    // @{
    //     @"Feature Flag 1's Name": @"Feature Flag 1's Key",
    //     @"Feature Flag 2's Name": @"Feature Flag 2's Key"
    // };
}];
Taplytics.getRunningFeatureFlags { (featureFlags) in
    // For example featureFlags will contain:
    // [
    //     @"Feature Flag 1's Name": @"Feature Flag 1's Key",
    //     @"Feature Flag 2's Name": @"Feature Flag 2's Key"
    // ]
}

Note: The block can return asynchronously once Taplytics properties have loaded. The block will return a NSDictionary with feature flag names as the key value, and feature flag keys as the value.


Sessions

By default, Taplytics defines a session as when a user is using the app with less than 10 minutes of inactivity. If the app has been backgrounded for 10 minutes, the next time the user opens the app it will be considered a new session. Similarly, if the app is entirely force closed, the next time the app is opened, it will be considered a new session.


Manually starting a new session

To manually force a new user session (ex: A user has logged in / out), there exists a startNewSession method.

If there is an internet connection, a new session will be created, and new experiments/variations will be fetched from Taplytics if they exist.

It can be used as follows:

[Taplytics startNewSession:^(BOOL success) {
    // New session here! Success will be false if this failed.
}];
Taplytics.startNewSession { (success) in
    // New session here! Success will be false if this failed.
}

Debugging

The shake menu allows you to switch variations on your device on the fly. You can use the shake functionality to open it, or you can call it in code like so:

[Taplytics showMenu];
Taplytics.showMenu()

Push Notifications

Setting up Push Notifications using Taplytics is simple. Follow the steps below to get started.

First and foremost, ensure that you have setup your Apple Push Certificates as per these docs.


Required Code for iOS 9 and below

For iOS and Taplytics to know that your app accepts Push Notifications, you must implement the following methods on your UIApplicationDelegate.

// Implement these methods for Taplytics Push Notifications
- (void)application:(UIApplication *)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
}

// Method will be called if the app is open when it recieves the push notification
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    // "userInfo" will give you the notification information
}

// Method will be called when the app receives a push notification
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    // "userInfo" will give you the notification information
    completionHandler(UIBackgroundFetchResultNoData);
}
func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {
}

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
}

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
}

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
    // "userInfo" will give you the notification information
}

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    // "userInfo" will give you the notification information
    completionHandler(UIBackgroundFetchResult.noData)
}

Required Code for iOS 10

For iOS 10, you'll need to implement the new UserNotification class to allow Taplytics and iOS to accept Push Notifications. You will need to change your UIApplicationDelegate header file to look something like the following:

#import <UIKit/UIKit.h>
#import <UserNotifications/UserNotifications.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate, UNUserNotificationCenterDelegate>

@end
import UIKit
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
    ...
}

You will also need to add the following methods to your UIApplicationDelegate

// Implement these methods for Taplytics Push Notifications
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
}

// Method will be called when the app recieves the push notification
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
   // "userInfo" will give you the notification information
   completionHandler(UIBackgroundFetchResultNoData);
}

// Method will be called if the app is open when it recieves the push notification
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler{
   // "notification.request.content.userInfo" will give you the notification information
   completionHandler(UNNotificationPresentationOptionBadge);
}

// Method will be called if the user opens the push notification
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
    // "response.notification.request.content.userInfo" will give you the notification information
    completionHandler();
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
}

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
}

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    // "userInfo" will give you the notification information
    completionHandler(UIBackgroundFetchResult.noData)
}

func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    // "notification.request.content.userInfo" will give you the notification information
    completionHandler(UNNotificationPresentationOptions.badge)
}

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    // "response.notification.request.content.userInfo" will give you the notification information
    completionHandler()
}

If you want your app to also support lower versions of iOS, you just need to add the missing methods described in the above section.


Register for Push Notifications

You'll also need to register for push notifications with iOS. When you do so, iOS will ask your users for permission and enable the ability to receive notifications to that device.

You'll need to enable a few capabilities on your app. Go into your project settings and find your project under Targets. Select the Capabilities tab and turn on Push Notifications and Background Modes. Under Background Modes, enable Remote Notifications.

1710

If you are not already registering for push notifications all you have to do is call registerPushNotifications: on Taplytics, and we take care of all the rest!

Please Note that calling this function will show the permission dialog to the user.

[Taplytics registerPushNotifications];
Taplytics.registerPushNotifications()

Register for Location Permissions (Optional)

For automated push campaigns using location based regions you will need to add the CoreLocation framework to your app, and request location permissions from your users. Taplytics will automatically update and manage the monitored regions on your device for your automated push campaigns.

You can handle asking for location permissions yourself, or you can use our provided method as seen below. But make sure that you request AuthorizedAlways permissions so that we can set regions.

// We will request AuthorizedAlways access to be able to set monitored regions
[Taplytics registerLocationAccess];
Taplytics.registerLocationAccess()

In order to allow the iOS location manager to successfully display a location request dialog to the user, the following properties must be added to the application's Plist settings:

NSLocationAlwaysUsageDescription
NSLocationWhenInUseUsageDescription

These values will be used by the OS to display the reason for requesting location access.


Receiving Push Notifications

To be able to send your users Push Notifications, we'll need you to upload your Apple Push certificates. Please follow this guide to do so.


Environments (Development and Production)

In order to separate your development devices and production devices, Taplytics automatically buckets devices into Development and Production buckets. We do this by looking at the provisioning profile that the app was built with.

Development Bucketing

If the app is built using a Development Provisioning Profile, we bucket the device into the Development environment. You can change this by forcing an environment.

All devices that fall into the Development environment, Taplytics will use the Development Push Notification Certificate to attempt to send push notifications to your devices.

Production Bucketing

If the app is built using a Distribution Provisioning Profile, we bucket the device into the Production environment. Note that this means that if you're distributing Ad-Hoc or Enterprise builds through a service like Testflight or Hockeyapp, then all those devices running those builds will fall into the Production environment. You can change this by forcing an environment.

All devices that fall into the Production environment, Taplytics will use the Production Push Notification Certificate to attempt to send push notifications to your devices.

Forcing an Environment

You can use the options attribute when you start Taplytics in order to force the environment that your devices are bucketed into. This can be especially useful for any Ad-Hoc and Enterprise distribution you might be doing.

Here's how you can achieve setting the Push environment:

// To bucket everyone into Production:
[Taplytics startTaplyticsAPIKey:@"SDK_KEY" options:@{@"pushSandbox":@0}];

// To bucket everything into Development:
[Taplytics startTaplyticsAPIKey:@"SDK_KEY" options:@{@"pushSandbox":@1}];
// To bucket everyone into Production:
Taplytics.startAPIKey("SDK_KEY", options: ["pushSandbox": 0])

// To bucket everything into Development:
Taplytics.startAPIKey("SDK_KEY", options: ["pushSandbox": 1])

Resetting Users

If you're using our User Attributes feature, you can easily disconnect a user from a device when they log out. This will prevent our system from sending push notifications to that device. It is especially important to reset the user when your push campaigns are targeted to a specific user. You can do this by calling our resetUser: function:

[Taplytics resetUser:^{
    // Finished User Reset
}];
Taplytics.resetUser {
  // Finished user reset
}

Rich Push Notifications (iOS 10+)

Implementing rich push notification support can help improve user engagement with your push notifications with image content attached. Rich push notifications make use of Notification Service Extension on iOS 10+ to display images attached to the push notifications. We currently support JPEG and PNG images sent through the Taplytics dashboard or API.

The max image size that can be uploaded is 10mb. Note that images are not downscaled and if an image is sent, the full file size of the crop will be used.

350

Create a Notification Service Extension

You'll need to add a Notification Service Extension to your app which is a small extension to your app that downloads the image attached to the notification and displays it as part of the notification.

To create the extension open File > New > Target in Xcode, select Notification Service Extension, then name your service extension and create it with language Swift.

1466

Once you've created the Notification Service Extension you should have a file called NotificationService.swift, open that up and edit its didReceiveRequestWithContentHandler method with the following code:

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

    // look for existance of taplytics data with image_url
    if let tlData = request.content.userInfo["taplytics"] as? [String: Any], let imageUrl = tlData["image_url"] as? String, let url = URL(string: imageUrl) {
        URLSession.shared.downloadTask(with: url) { (location, response, error) in
            if let location = location {
                // get path in temp directory for file
                let tempFileURL = URL(string: "file://".appending(NSTemporaryDirectory()).appending(url.lastPathComponent))!

                var attachment: UNNotificationAttachment?
                do {
                    // move file into temp directory to be displayed by Notification Service Extension
                    if (FileManager.default.fileExists(atPath: tempFileURL.relativePath)) {
                        try FileManager.default.removeItem(at: tempFileURL)
                    }
                    try FileManager.default.moveItem(at: location, to: tempFileURL)

                    // generate image attachment
                    attachment = try UNNotificationAttachment(identifier: "tl_image", url: tempFileURL)
                } catch let error {
                    print("Error: \(error)")
                }

                // Add the attachment to the notification content
                if let attachment = attachment {
                    self.bestAttemptContent?.attachments = [attachment]
                }
            }

            // render notification
            self.contentHandler!(self.bestAttemptContent!)
        }.resume()
    } else {
        // If there is no image payload render the notification as a normal notification.
        self.contentHandler!(self.bestAttemptContent!)
    }
}

What this code is doing is looking for any data attached to the push payload under a taplytics object, and specifically looking for a taplytics.image_url url to download an image from, which will then start a downloadTask for that url. Once the image is downloaded it will move the image to the Extension's temp directory and add the image as a notification attachment to the push, and finally render the notification to display it.

Any push notifications sent without the image url attached to its data will display as normal by iOS.


Push Notification Payload

If you are handling push notifications with custom payloads, the custom data key/values will be added to the base object as seen below:

{
    "custom_data_key": "custom_data_value",
    "aps": {
        "alert": "Test Push",
        "badge": 1,
        "sound": "default",
        "tl_id": "",
        "content-available": "1",
        "mutable-content": "1"
    },
    "taplytics": {
        "image_url": ""
    }
}

Troubleshooting

Here are some of the common errors we've run into when installing the Taplytics iOS SDK and the fixes to go with them. We hope it helps keep the installation smooth, if you feel like you need a hand, please don't hesitate to reach out. We'd love to hear from you.


"OBJC_CLASS$_Taplytics" not found

Undefined symbols for architecture x86_64:
  "_OBJC_CLASS_$_Taplytics", referenced from:
      objc-class-ref in AppDelegate.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

If you encounter the above error when attempting to build and run your app in XCode, please make sure Taplytics.framework is in the Link Binary With Libraries.

To navigate to the correct menu: Project > Build Phases > Link Binary With Libraries


"_utf8_nextCharSafeBody" build error

Undefined symbols for architecture x86_64:
  "_utf8_nextCharSafeBody", referenced from:
      -[TL_SRWebSocket _innerPumpScanner] in Taplytics(TL_SRWebSocket.o)
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

If you encounter this error when attempting to build and run your app in XCode, the entry for libicucore.dylib might be missing, please make sure that it has been added to your Link Binary With Libraries list.

To navigate to the correct menu: Project > Build Phases > Link Binary With Libraries


##Terminating app due to uncaught exception 'NSInvalidArgumentException'
...
libc++abi.dylib: terminating with uncaught exception of type NSException

If you encounter this error when attempting to build and run your app in XCode, the -ObjC flag is missing under Other Linker Flags.

To navigate to the correct menu: Project > Build Settings > Other Linker Flags = "-ObjC"


"OTHER_LDFLAGS" Error

If you're running into an "OTHER_LDFLAGS" error when trying to build your podfile using $pod install, we have a simple fix for you!

Here's an example of the error:

[!] The target `Taplytics Example App [Debug]` overrides the `OTHER_LDFLAGS` build setting defined in `Pods/Pods.xcconfig'.
  - Use the `$(inherited)` flag, or
  - Remove the build settings from the target.
[!] The target `Taplytics Example App [Debug - Release]` overrides the `OTHER_LDFLAGS` build setting defined in `Pods/Pods.xcconfig'.
  - Use the `$(inherited)` flag, or
  - Remove the build settings from the target.

The Fix:

In XCode, go to "Build Settings" and find the "Other Linker Flags" setting under "Linking". The entry for this setting should be bolded.

OPTION A
Select the value for the "Other Linker Flags" setting and hit backspace. This should remove the bolded value, and a list of non-bolded values should appear. These are all the flags from the CocoaPod.

OPTION B
If you need to have your own flags declared, include $(inherited) in the list of flags. This should append all the flags defined in the CocoaPod to your list of flags.

The Additional Fix:

If the fix above still doesn't address the compilation error, please try clearing the bolded values or add $(inherited) to the following Build Settings: Framework Search Paths Header Search Paths