Contentsquare iOS Integration
Get Started
Add Contentsquare to your app
Our iOS SDK is shipped as a .xcframework
which you need to add as a dependency of your project.
How to include it
The SDK requires Xcode 13 or later, if you are using older versions of Xcode reach out to your Contentsquare contact for more information.
[tab] Using Swift Package Manager
-
In Xcode, add the following link via
File > Add Packages…
:https://github.com/ContentSquare/CS_iOS_SDK.git
-
To ensure the library can start properly you will need to add
-ObjC
as a linker flag underBuild Settings
>Linking
>Other Linker Flags
.
[tab] Using Carthage
Add the following line to your Cartfile:
binary "https://raw.githubusercontent.com/ContentSquare/CS_iOS_SDK/master/carthage/ContentsquareModule.json"
github "apple/swift-protobuf" ~> 1.0
Then run
carthage update --platform ios --use-xcframeworks
Drag and drop ContentsquareModule.xcframework
, SwiftProtobuf.xcframework
from the Carthage/Build folder to your targets’ General settings tab, in the Frameworks, Libraries, and Embedded Content section.
[tab] Using CocoaPods
Our SDK can be linked dynamically or statically:
Dynamic linking
Dynamic linking is the default behavior with use_frameworks!
,
Add the following line to your Podfile:
pod 'CS_iOS_SDK'
Static linking
If you specify static linking in your Podfile with use_frameworks! :linkage => :static
,
Follow these steps:
-
Add the following line to your Podfile:
Podfilepod 'CS_iOS_SDK_STATIC'
-
To ensure the library can start properly you will need to add
-ObjC
as a linker flag underBuild Settings
>Linking
>Other Linker Flags
.
Known Issues
-
Updating
IPHONEOS_DEPLOYMENT_TARGET
in yourpost_install
may have the following error.dyld[47592]: Symbol not found: __ZN5swift34swift50override_conformsToProtocolEPKNS_14TargetMetadataINS_9InProcessEEEPKNS_24TargetProtocolDescriptorIS1_EEPFPKNS_18TargetWitnessTableIS1_EES4_S8_E Referenced from: Xxxx
Workaround: Skip updating IPHONEOS_DEPLOYMENT_TARGET for SwiftProtobuf by following these steps.
-
Update your
post_install
to the following onePodfilepost_install do |installer| installer.pods_project.targets.each do |target| # Skip updating IPHONEOS_DEPLOYMENT_TARGET for SwiftProtobuf next if target.to_s == 'SwiftProtobuf' target.build_configurations.each do |config| config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = 'XX.X' end end end
-
Rerun
pod install
. -
Clean Builder Folder.
-
Rebuild.
-
[tab] Manual Integration
Our SDK can be linked dynamically or statically:
Dynamic linking
Get the manual integration framework
- Go to the iOS SDK GitHub repository.
- Find the newest version available (unless instructed otherwise by your CS contact).
- Under
Assets
you should be able to findContentsquareModuleDynamicManually.xcframework.zip
, download the file.
Include the framework
- Unzip
ContentsquareModuleDynamicManually.xcframework.zip
and you should see a folder namedContentsquareModule
containing 2 items:ContentsquareModule.xcframework
SwiftProtobuf.xcframework
- Copy
ContentsquareModule
to any folder in your project. - In your
target
->General
->Frameworks, Libraries and Embedded Content
, addContentsquareModule.xcframework
,SwiftProtobuf.xcframework
by clicking "+" -> "Add Other..." -> "Add Files...". - Clean build folder and run.
Static linking
Get the manual integration framework
- Go to the iOS SDK GitHub repository.
- Find the newest version available (unless instructed otherwise by your CS contact).
- Under
Assets
you should be able to findContentsquareModuleStaticManually.xcframework.zip
, download the file.
Include the framework
- Unzip
ContentsquareModuleStaticManually.xcframework.zip
and you should see a folder namedContentsquareModule
containing 3 items:ContentsquareModule.xcframework
Resources/ContentsquareBundle.bundle
SwiftProtobuf.xcframework
- Copy
ContentsquareModule
to any folder in your project. - In your
target
->General
->Frameworks, Libraries and Embedded Content
, addContentsquareModule.xcframework
,SwiftProtobuf.xcframework
by clicking "+" -> "Add Other..." -> "Add Files...". - Add
ContentsquareBundle.bundle
to your target, make sure it has been added to yourtarget
->Build Phases
->Copy Bundle Resources
. - To ensure the library can start properly you will need to add
-ObjC
as a linker flag underBuild Settings
>Linking
>Other Linker Flags
. - Clean build folder and run.
Start the SDK
Automatic start (default)
By default, you do not need to do anything to start the SDK. Now that the SDK is a dependency of your app, it will autostart itself when your application starts.
Manual start
If you prefer to control if and when the SDK starts, you can disable the autostart feature and programmatically start the SDK when needed.
The first step is to add the required key to your Info.plist
. The key is CSDisableAutostart
of type boolean. To disable the autostart, you need to set its value to true
:
-
Disable autostart using Xcode:
-
Disable autostart using a text editor:
Info.plist<key>CSDisableAutostart</key> <true/>
Then just call start
in the application:didFinishLaunchingWithOptions:
of your UIApplicationDelegate
like this:
Contentsquare.start()
When you call the start method, the SDK automatically starts tracking users' interactions with the app (gestures, lifecycle events and crashes).
Validate SDK integration
When the SDK starts, you should see a log like this one:
// success case
Contentsquare SDK v[SDKVersionNumber] starting in app:: [your.bundle.id]
// fail case
// << No public fail log
If the SDK does not seem to work, it might mean that your app's bundle id is not tied to any Contentsquare project. In that case, you will have to communicate all variants of your app's identifier to your Contentsquare contact to be added as a project.
Now that the SDK runs in your app, you will want to implement calls to our SDK to track screenviews, track transactions, and more.
Sample app
For best implementation practices of our library, explore the Contentsquare for iOS sample app.
In-app features
Alongside its tracking capabilities, the SDK embeds some features aimed at Contentsquare users such as Snapshot Capture and SDK Logs.
Implement in-app features
In order to allow Contentsquare users to enable in-app features, you must perform 2 implementation tasks:
1. Add the custom URL scheme in your app Info
You have to allow your app to be opened via a custom URL scheme which can be done using one of the following methods:
[tab] Xcode
- Open your project settings
- Select the app target
- Select the
Info
settings - Scroll to
URL Types
- Set the URL scheme to
cs-$(PRODUCT_BUNDLE_IDENTIFIER)
[tab] Text editor
-
Open the
Info.plist
of your project -
Add the following snippet:
Info.plist<key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLSchemes</key> <array> <string>cs-$(PRODUCT_BUNDLE_IDENTIFIER)</string> </array> </dict> </array>
[tabend]
2. Call the SDK when the app is launched via a deeplink
Depending of the project, there are multiple ways to handle the deeplink opening. Choose the method matching your project structure:
[tab] SwiftUI pattern
In the body
of your main App struct, add the onOpenURL
modifier and call the Contentsquare
SDK to handle the URL:
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
MyView()
.onOpenURL { url in
Contentsquare.handle(url: url)
}
}
}
}
[tab] SceneDelegate pattern
In your WindowSceneDelegate
class, you need to:
-
Update
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
with:if let url = connectionOptions.urlContexts.first?.url { Contentsquare.handle(url: url) }
-
Complete or implement
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>)
with:if let url = URLContexts.first?.url { Contentsquare.handle(url: url) }
[tab] AppDelegate pattern
In your AppDelegate
class, complete or implement the function application(app, open url:, options:)
with: Contentsquare.handle(url: url)
[tabend]
Enable in-app features
Once the implementation is done, in-app features can be enabled in different ways:
Scan the QR code
If you have access to the Contentsquare platform, you can open the in-app features modal from the menu and scan the QR code displayed with your iPhone.
Use the custom link for simulator
If you have access to the Contentsquare platform, you can open the in-app features modal from the menu and select "Copy link" (to copy the deeplink) and paste it in Safari on your Simulator to trigger the in-app features.
Using the Terminal
In a Terminal console, open an URL in your current simulator with the following command (replacing "CUSTOM_LINK" with yours):
xcrun simctl openurl booted CUSTOM_LINK
Debugging and Logging
Contentsquare provides Logging capabilities that allow you to see the raw event data logged by your app in the MacOS Console App, Xcode or in the Contentsquare platform. This is very useful for validation purposes during the instrumentation phase of development and can help you discover errors and mistakes in your analytics implementation and confirm that all events are being logged correctly.
Viewing Logs in Xcode or the MacOS Console app
By default, almost all logs are disabled. There is only one log that is always visible to notify that the SDK has started:
CSLIB ℹ️ Info: Contentsquare SDK v{{SDKversionNumber}} starting in app: {{bundleID}}
In order to enable all logs, you simply need to activate in-app features. Logging is directly linked to in-app features state: it starts when in-app features is enabled and stops when you disable in-app features.
To view logs:
- Make sure the device you are using is plugged to your Mac or is on the same Wi-Fi network (skip this step if you are using a simulator)
- Start the MacOS Console app (make sure that info messages are included: Choose Action > Include Info Messages) or Xcode.
- Filter logs on
CSLIB
Viewing logs in the Contentsquare platform
To view logs directly on the platform, you can use Log visualizer. Log visualizer is a helper tool to see SDK logs without logging tools. It requires having platform access for your project and enabling in-app features. See the SDK Log Visualizer Help Center Article for more information.
Snapshot Capture
In order to unlock the full data-visualization capabilities of Contentsquare, the SDK provides a way to capture snapshots of your app screens. These snapshots can only be taken by Contentsquare's users on their device. They are not captured from your end-users device. It means your Personal Data is safe, as long as you use a test user account.
Snapshots are used in the Zoning Analysis module to look at zone-level metrics (Tap rate, Swipe rate...):
Privacy
The Contentsquare SDK is compliant with the App Store Privacy guidelines as well as with the EU General Data Protection Regulation (GDPR).
Consult our Privacy Center and Privacy Policy.
Privacy practices to declare to App Store
From December 8th, 2020, Apple is going to ask Apps publishers to declare app's privacy practices, including third-party partners such as Contentsquare. See Apple's website for more information.
Types of data
Here is the list of data type we collect:
Categories | Data type | Storage retention |
---|---|---|
Identifiers | User ID | 13 months |
Usage Data | Product Interaction | 13 months |
Diagnostics | Crashes | 13 months |
Purpose
Contentsquare's purpose should be categorized as: Analytics.
Data linked to the user
All collected data is linked to the user via the Contentsquare User ID we generate. This user ID and all collected data are stored for 13 months.
Tracking
Contentsquare does not match the "tracking" definition of Apple which is related to advertising or sharing with data broker.
Personally Identifiable Information
User ID
We do not use IDFA or any ad related or third-party information to identify the user. The SDK generates a unique user ID (UUID) (random hash) which is specific to users on their device. Contentsquare can't identify a user across devices. We don’t persist the UUID when the app is deleted and re-installed. The SDK generates a new UUID after install or re-install. This user ID is not shared with any third parties.
What is not collected
- IDFA or any other ad related user identifier
- Passwords from password fields
- Geo location information
- Information from contacts, emails, or any other user identifiable information
- Text from labels, buttons, and any other widget which contains text
- Accessibility information from any widget which the user interacts with
- Labels printed on the screen of any kind
Handling User Consent
The App Store guidelines regarding privacy (see section 5.1.1 ii Permissions) states that:
"Apps that collect user or usage data must secure user consent for the collection, even if such data is considered to be anonymous at the time of or immediately following collection."
Contentsquare collects usage data on your app. By default, the SDK will consider every new user to be opted-out. To start tracking, the SDK Opt-in API must be called.
You are responsible for creating the UI asking users for their consent and allowing them to manage their privacy settings and then calling the appropriate Contentsquare following functions (opt-in, opt-out, forget me...).
Opt-in
Use the Opt-in API to get user consent. Calling this API will generate a user ID and initiate tracking.
[tab] Swift
Contentsquare.optIn()
[tab] Objective-C
[Contentsquare optIn];
[tabend]
Opt-Out
Permanently breaking the link and stopping all data collection.
When this API is called, tracking stops immediately, all settings are reset (Session number, Page number...) and all files and directory regarding to Contentsquare are deleted. This means that the user ID is deleted. The SDK will never track and collect any data from the user's phone unless the Opt-in API is called again.
[tab] Swift
Contentsquare.optOut()
[tab] Objective-C
[Contentsquare optOut];
[tabend]
Forget me
Permanently breaking the link between the collected data and actual user.
This resets all settings and deletes all files and directories from the user's device (User ID is deleted). If user is opted in, next time user starts the app, the SDK will re-start its collection mechanisms as if this was the first ever run for a new user, under a new user id. Configurations will be fetched from the server and application tracking will be on.
[tab] Swift
Contentsquare.forgetMe()
[tab] Objective-C
[Contentsquare forgetMe];
[tabend]
Give me my data
We allow the client to provide to their users their Contentsquare user ID.
This ID is a non binding identifier which can be used to make a data request to Contentsquare. You are able to get an ID only if the user is not Opted-out.
[tab] Swift
Contentsquare.userID
[tab] Objective-C
[Contentsquare userID];
[tabend]
Pause / Resume Tracking
Although we do not gather any humanly readable text from the user's screens, we understand that there may be some areas that you want to completely exclude from tracking. For this reason we also support pausing and resuming the complete tracking mechanism.
[tab] Swift
Contentsquare.stopTracking()
// ...
Contentsquare.resumeTracking()
[tab] Objective-C
[Contentsquare stopTracking];
// ...
[Contentsquare resumeTracking];
[tabend]
Disable user tracking across sessions
If you don't want to link the different sessions of a user to the same userID, follow these instructions to reset the userID at each app start:
-
Implement the following:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) { Contentsquare.start() Contentsquare.optOut() Contentsquare.optIn() … }
- Using the manual start method of the SDK will ensure that opt-out is called right after the start of the SDK (no event tracked in between).
- Calling opt-out will delete the previous userID.
- Calling opt-in will set a new one.
Track Screens
Contentsquare aggregates user behavior and engagement at the screen level. To do so, it is required to track screen transitions by calling a dedicated API. When the API is called, the SDK logs a screenview event that identifies the new screen with the screen name provided.
[tab] Swift
import ContentsquareModule
Contentsquare.send(screenViewWithName: String, cvars: [CustomVar] = [])
[tab] Objective-C
@import ContentsquareModule;
[Contentsquare sendWithScreenViewWithName:(NSString * _Nonnull)];
// or
[Contentsquare sendWithScreenViewWithName:(NSString * _Nonnull) cvars:(NSArray<CustomVar *> * _Nonnull)]; // To add custom variables to screen tracking
[tabend]
Screen name handling
The screen name length is not limited on the SDK side. However, the limit is 2083 characters on the server side.
Screenview after app in background
The SDK triggers a screenview automatically after the app is put in background and foreground, as long as a screenview with a screen name has been triggered previously. It will use the last screen name set.
Implementation recommendations
From a functional standpoint, we expect a screenview to be sent:
- Right after the SDK has started
- When the screen appears
- When a modal/pop-up is closed and the user is back on the screen
- When the app is put in the foreground (after an app hide)
We advise you to take a look at our reference implementations of screenviews in our sample app. Learning from them is the best way to make sure your implementations are correct. Regardless, here is some general advice.
When to send your first screenview
Most of the events collected by the SDK require you to send a screenview event first so they can be associated to that screen, otherwise they will be discarded. If you need to collect events right from app launch, you should trigger a screenview event right after the SDK has started.
Automatic start
If the SDK is started automatically, you should send your first screenview when your app's life cycle begins. For instance, you can send it in application:didFinishLaunchingWithOptions:
of your UIApplicationDelegate
if you are using UIKit, or in the init()
of your App
struct if you are using SwiftUI:
[tab] UIKit
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// ...
Contentsquare.send(screenViewWithName: "Launch screen")
// ...
return true
}
[tab] SwiftUI
@main
struct MyApp: App {
init() {
// ...
Contentsquare.send(screenViewWithName: "Launch screen")
// ...
}
}
Manual start
If the SDK is started manually, you should send your first screenview right after calling start()
:
Contentsquare.start()
Contentsquare.send(screenViewWithName: "Launch screen")
General rules
[tab] UIKit
As a general rule of thumb, you should send your screenviews in viewWillAppear(_ animate: Bool)
. Be aware that only doing this might not cover all your cases though, and you might need to send screenview events in other parts of your code.
[tab] SwiftUI
If you are using SwiftUI to build your application, you should send your screenview in .onAppear()
. Be aware that only doing this might not cover all your cases though, and you might need to send screenview events in other parts of your code.
import ContentsquareModule
var body: some View {
Text("My first screen")
.foregroundColor(Color.blue)
.onAppear() {
Contentsquare.send(screenViewWithName: "First screenView")
}
}
ScrollViews with paging
Be careful when tagging screens with paged ScrollViews: if you want to create a screenview event whenever the user changes the page, do so in UIScrollViewDelegate's scrollViewDidEndDecelerating(_ scrollView: UIScrollView)
function. Make sure that you do not trigger a double tag when the screen appears, or when you come back to the screen after the app being hidden. Also make sure not to send a screenview when the user slightly interacts with a page, but without really changing pages.
Popups and Modals
When dealing with popups and modals, if you are having issues triggering a screenview event for the underlying view when the modal closes we have the following advice. You could overcome this issue by using a delegate pattern, and setting the underlying screen as the modal's delegate. The modal would call some delegate method when it is dismissed. The underlying screen would implement that method, where it would make the screenview.
Back navigation and navigation between screens
Make sure that screenview events will be triggered when a user will go to the previous screen. i.e. Home > Profile > Home, it is expected to have a screenview event for the Home screen that might be reached with the back navigation button. Normally, if you send your screenviews in viewWillAppear
, this should work fine.
Redirecting the user to another screen (authentication, home) when closing the app/re-opening the app
For some apps, you might want to redirect users whenever they hide your app, for example for security purposes (bank apps, password managers, etc...). If that is the case, pay specific attention to the way screenview events are sent, in order not to track a screen which is not actually shown users.
Application life cycle
When your application returns from the background to the foreground, the SDK automatically logs a screenview with the title of the last logged screenview, so you don't have to handle this transition yourself.
How to name screens
As a general rule, keep distinct screen names under 100. As they are used to map your app in Contentsquare, you will want something comprehensive.
Separate words with space, dash or underscore characters
If you want to generate screen names including more than one word, it is best to separate them and to do so using space, dash or underscore characters. Contentsquare handles automatically the formatting for them.
Example: For a sub-category list of a retail app, use Home & Living - Home Furnishings
instead of .homeLivingHomeFurnishings
Use screen template/layout names
As a general recommendation, use names referring to the screen template/layout rather than referring to the specific content (data). This will help:
- To keep the number of distinct screen names low and therefore make Contentsquare easier to use
- Remove the risk of sending Personal Data to Contentsquare
List of screen types falling into that category: Product detail, Event detail, Conversation/Chat, User profile...
Multiple layouts/states for one screen
In some cases, there will be screen that can have different layouts/states depending on the user context. In this situation, it would be interesting to append the layout/state value to the screen name. Examples:
- Home screen of a travel app adapting its layout on the user context:
State Screen name No trip planned Home - no trip
Trip planned Home - trip planned
Trip about to start Home - upcoming trip
Trip in progress Home - trip in progress
- Product detail screen of an e-commerce app with different layouts depending on the type of product:
State Screen name Default template Product detail
Template with suggested products Product detail - Suggestions
Template with bundled products Product detail - Bundle
How to track sessions & screens metadata
Custom Variables
Custom variables are designed to provide additional details about the screenview or user information during end-user engagement. These variables can encompass various aspects, including screen-specific attributes or user-related information, and serve as a means to enhance data collection and analysis.
Example:
- Product name: "Acme Widget"
- Product color: "red"
- Product price: "$19.99"
Learn how to setup Custom Variables
Dynamic Variables
Dynamic variables are used to provide additional information on the session, such as the name of a campaign or the version of a variation for an A/B test integration. They can be sent at any time during a session and do not require sending a screenview. Note that dynamic variables should be used only in segments.
Example:
- Campaign name: "Summer Sale 2023"
- Variation version: "Version A"
Learn how to setup Dynamic Variables
Track Custom Variables
General principles
Usage
Custom variables are additional information on the screen, the user or the session, sent within screenviews.
For example, they can include information on the current layout of a screen, like day/night mode.
Limits
On the server side
- It is possible to save up to 20 distinct custom variable per screenview. If more are received, only the first 20 custom variables, based on their
index
value, will be kept. - The
index
value of the custom variable is used to determine which custom variables to be kept. Onlyindex
value between 1 and 20 will be taken into account.
On the SDK side
- Every custom variable is composed of
index
,name
andvalue
. - If you are using the same
index
twice in the same screen, only the first (name
,value
) pair associated with theindex
will be kept. - In case
name
(max. 512 characters) orvalue
(max. 255 characters) maximum character length is reached, the SDK will automatically trim the exceeding characters. - If
name
orvalue
are empty, the SDK will instead send the literal string"cs-empty"
. - Use a consistent index for a given custom var within an application — for instance, if the "screen layout" is collected with an
index
of 3, use the slot 3 for this information on every screen of the application.
Defining custom variables
To define and send custom variables, you just need to use the following implementation:
[tab] Swift
import ContentsquareModule
let cvar1 = CustomVar(index: 1, name: "CustomVarName1", value: "CustomVarValue1")
let cvar2 = CustomVar(index: 2, name: "CustomVarName2", value: "CustomVarValue2")
Contentsquare.send(screenViewWithName: "ScreenName", cvars: [cvar1, cvar2])
[tab] Objective-C
@import ContentsquareModule;
CustomVar *cvar1 = [[CustomVar alloc] initWithIndex:1
stringName:@"CustomVarName1"
stringValue:@"CustomVarValue1"];
CustomVar *cvar2 = [[CustomVar alloc] initWithIndex:2
stringName:@"CustomVarName2"
stringValue:@"CustomVarValue2"];
[Contentsquare sendWithScreenViewWithName:@"ScreenName" cvars:@[cvar1, cvar2]];
[tabend]
Track Transactions
To associate a user's session with their potential purchases (and corresponding revenue), you must send the transaction via a dedicated API. For each transaction, we send:
- Price (mandatory)
- Currency (mandatory)
- Transaction ID (optional)
Currency
The currency is conforming to the ISO 4217 standard. The currency can be passed either as "alphanumeric code" or "numeric code".
If the currency passed doesn't match the supported currencies, the SDK will send a currency value of "-1". It will be processed as the default currency of the project.
[tab] Swift
//numeric currency
Contentsquare.send(transaction: CustomerTransaction(id: String?, value: Float, currency: Currency))
//alphanumeric currency
Contentsquare.send(transaction: CustomerTransaction(id: String?, value: Float, currency: String))
[tab] Objective-C
//numeric currency
CustomerTransaction *transaction = [[CustomerTransaction alloc] initWithId:(NSString * _Nullable)
value:(float)
currency:(enum Currency)];
[Contentsquare sendWithTransaction:transaction];
//alphanumeric currency
CustomerTransaction *transaction = [[CustomerTransaction alloc] initWithId:(NSString * _Nullable)
value:(float)
stringCurrency:(NSString * _Nonnull)];
[Contentsquare sendWithTransaction:transaction];
[tabend]
Track Dynamic Variables
General principles
Usage
Dynamic variables are additional information on the session that can be used to segment sessions.
For example, they can include information on the A/B Test variations displayed to the current user.
Limits
On the server side
- It is possible to save up to 40 distinct dynamic variable keys per screenview. If more are received, only the first 40 keys will be kept.
- If you are using the same key twice in the same screen, the last value associated with the key will be collected.
On the SDK side
- Every dynamic variable is composed of a pair of key (max. 512 characters) and value (max. 255 characters string or a
UInt32
between 0 and 232 - 1). In case these maximums length are reached, the SDK will automatically trim the exceeding characters. - If key or value are empty, the SDK will instead send the literal string "cs-empty".
Defining dynamic variables
To define and send a dynamic variable, you just need to use the following API:
[tab] Swift
//string value
Contentsquare.send(dynamicVar: DynamicVar(key: String, value: String))
//int value
Contentsquare.send(dynamicVar: DynamicVar(key: String, value: UInt32))
[tab] Objective-C
//string value
DynamicVar *dynamicVarString = [[DynamicVar alloc] initWithKey:(NSString * _Nonnull)
stringValue:(NSString * _Nonnull)
error:(NSError * _Nullable __autoreleasing * _Nullable)];
[Contentsquare sendWithDynamicVar:dynamicVarString];
//int value
DynamicVar *dynamicVarInt = [[DynamicVar alloc] initWithKey:(NSString * _Nonnull)
intValue:(uint32_t)
error:(NSError * _Nullable __autoreleasing * _Nullable)];
[Contentsquare sendWithDynamicVar: dynamicVarInt];
[tabend]
Type of the value — The value can be either a whole number or a string. For each case, available features won't be the same in the Contentsquare app:
- For whole numbers, you will be able to do some algebra. Example: sessions with dynamic variable key = "numberOfFriends" and value >= 10
- For strings, auto-completion and Regular Expression will be available. Example: sessions with dynamic variable key = "accountType" and value = "Premium"
Sending a dynamic variable for each session
You may want to send a dynamic variable for each session (like the user's country or store). While triggering the dynamic variable at app launch will cover most cases, it will not be enough. A session can start when the app is put in foreground, after staying in background for more than 30 minutes. See Session definition section for more information.
That is why we also recommend sending such dynamic variable every time the app enters foreground.
You can use didBecomeActiveNotification
notification and sceneDidBecomeActive(_:)
callback to detect foreground and trigger a dynamic variable.
Track WebViews
To enable WebView tracking, it is required to build a JavaScript Bridge between the content of the WebView and the native SDK. To do so, you have to implement the Contentsquare Web Tracking Tag in the web pages called in your app WebViews. Refer to this documentation for more details:
📚 Mobile Apps WebView Tracking Documentation
Once the Web Tracking Tag is implemented in the web pages, the following native API needs to be called to establish the JavaScript Bridge:
[tab] Swift
// Start tracking the given WKWebView
Contentsquare.register(webView: WKWebView)
// Stop tracking the given WKWebView
Contentsquare.unregister(webView: WKWebView)
[tab] Objective-C
// Start tracking the given WKWebView
[Contentsquare registerWithWebView:(WKWebView * _Nonnull)];
// Stop tracking the given WKWebView
[Contentsquare unregisterWithWebView:(WKWebView * _Nonnull)];
[tabend]
At the UIViewController level, call the register method above in the viewWillAppear method. And unregister the WebViews in the view controllers viewWillDisappear method
Validate WebView tracking
Validating the implementation on the native side
Once you arrive on the screen with the tracked WebView, you should see the following log:
CSLIB ℹ️ Info: WebView tracking enabled on native side for page: [URL]. Waiting for Web Tracking Tag messages…
Once the web page is loaded in the WebView, you should see the following log:
CSLIB ℹ️ Info: WebView navigated to new page: [URL]. Waiting for Web Tracking Tag messages…
Validating the implementation on the web side
Once the Web Tracking tag is detected in the web page, you should see the following log:
CSLIB ℹ️ Info: Web Tracking Tag is detected on page: [URL]
Validating pageview and gestures tracking on the web side
-
Page views fired by the Web Tracking Tag in the same format as for screen views:
CSLIB ℹ️ Info: Screenview - Screen name: "{{page name given}}" - Screen number: 11
-
Taps and swipes detected by the Web Tracking Tag in the same format as for defaults taps and swipe but with the end of the target containing HTML DOM elements values:
CSLIB ℹ️ Info: Tap - Target: ...>UIViewControllerWrapperView:eq(0)>UIView:eq(0)>WKWebView:eq(0)|webview|img#picture-holder
Session Replay
In order to implement Session Replay, go to Mobile SDK Session Replay documentation.
Error Analysis 🆕
In order to implement Error Analysis, go to iOS SDK Error Analysis documentation.
Use Adobe Analytics
Analyze your data from anywhere in the customer journey using your Adobe Analytics segments.
Contentsquare allows you to use your Adobe Analytics segments in every Contentsquare feature (Journey Analysis, Page Comparator, Zoning Analysis, Session Replay).
Prerequisites
- Follow the instructions from Adobe Analytics for mobile apps
- Add the AEPAnalytics iOS SDK to your project
Code implementation
[tab] Swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// ... your existing code ..
MobileCore.registerExtensions([Identity.self, Analytics.self, ....], {
// ... your existing code ..
self.sendCSMatchingKeyIfNeeded()
})
// ... your existing code ..
}
func sendCSMatchingKeyIfNeeded() {
let csMatchingKeyTimestampKey = "csMatchingKey_ts"
let csMatchingKeyValidityMs = 1_800_000 // 30 minutes
let currentTimestamp = Int(Date().timeIntervalSince1970 * 1000)
let csMatchingKeyIsPreviousTimestamp = UserDefaults.standard.integer(forKey: csMatchingKeyTimestampKey)
guard (currentTimestamp - csMatchingKeyIsPreviousTimestamp) > csMatchingKeyValidityMs else {
return
}
UserDefaults.standard.set(currentTimestamp, forKey: csMatchingKeyTimestampKey)
let csMatchingKey = "csMatchingKey"
let csMatchingKeyValue = "\(Double.random(in: 0..<1))_\(currentTimestamp)"
Contentsquare.send(dynamicVar: DynamicVar(key: csMatchingKey, value: csMatchingKeyValue))
MobileCore.track(state: "csMatchingKey_state", data: [csMatchingKey: csMatchingKeyValue])
}
[tab] Objective-C
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// ... your existing code ..
[AEPMobileCore registerExtensions:@[AEPMobileEdgeIdentity.class, AEPMobileAnalytics.class] completion:^{
// ... your existing code ..
[self sendCSMatchingKeyIfNeeded];
}];
// ... your existing code ..
}
- (void)sendCSMatchingKeyIfNeeded {
NSString *csMatchingKeyTimestampKey = @"csMatchingKey_ts";
NSInteger csMatchingKeyValidityMs = 1800000; // 30 minutes
NSInteger currentTimestamp = (NSInteger)([[NSDate date] timeIntervalSince1970] * 1000);
NSInteger csMatchingKeyIsPreviousTimestamp = [[NSUserDefaults standardUserDefaults] integerForKey:csMatchingKeyTimestampKey];
if ((currentTimestamp - csMatchingKeyIsPreviousTimestamp) <= csMatchingKeyValidityMs) {
return;
}
[[NSUserDefaults standardUserDefaults] setInteger:currentTimestamp forKey:csMatchingKeyTimestampKey];
NSString *csMatchingKey = @"csMatchingKey";
NSString *csMatchingKeyValue = [NSString stringWithFormat:@"%f_%f", (double)arc4random() / UINT32_MAX, currentTimestamp];
DynamicVar *csMatchingKeyDynamicVar = [[DynamicVar alloc] initWithKey:csMatchingKey
stringValue:csMatchingKeyValue
error:(NSError * _Nullable __autoreleasing * _Nullable)];
[Contentsquare sendWithDynamicVar:csMatchingKeyDynamicVar];
[AEPMobileCore trackState:@"csMatchingKey_state" data:@{csMatchingKey:csMatchingKeyValue}];
}
[tabend]
Use Google Tag Manager
If you are using Firebase to track events such as screenviews or transactions, you can trigger these events for Contentsquare with minimal effort, thanks to Google Tag Manager.
Prerequisite
If you are following this Google Tag Manager integration process, you should already have followed the Google Tag Manager + Firebase setup as described in Google's Tag Manager + Firebase: Getting Started
You also need to integrate the Contentsquare SDK see section: Add Contentsquare to your app.
Screenview events
This section covers how to trigger a Contentsquare screenview event for every Firebase screenview event. Firebase allows you to automatically logs screenviews but also to trigger them manually for an exhaustive coverage of your app screens.
Variables
Contentsquare screenview events require to pass the screen name as a parameter.
Firebase has 2 ways to track screens:
1. Variable for automatically tracked screens
Firebase’s screen_view
events have an _sc
parameter (for screen class) which we can use as parameter for screens that are automatically tracked.
We will create a variable called Automatic Screenview Name with:
- The Variable Type set to Event Parameter
- The Event Type set to Custom Parameter
- The Event Parameter Key set manually to
_sc
2. Variable for manually tracked screens
Firebase’s screen_view
events have a _sn
parameter (for screen name) which we can use as parameter for screens that are manually tracked.
We will create a variable called Manual Screenview Name with:
- The Variable Type set to Event Parameter
- The Event Type set to Custom Parameter
- The Event Parameter Key set manually to
_sn
We can also create variables for our own custom parameters, for example, we can send a screen_view
event like this:
Analytics.logEvent(AnalyticsEventScreenView, parameters: [
AnalyticsParameterScreenName: "MyScreenName",
"is_user_logged_in": "true"
])
and create a variable called Is User Logged In with:
- The Variable Type set to Event Parameter
- The Event Type set to Custom Parameter
- The Event Parameter Key set manually to
is_user_logged_in
Check out Configure variables in Tag Manager for more information.
Screenview trigger
We will need to tell Google Tag Manager when to send a Contentsquare screenview, and for that we need to create a Trigger. Firebase sends screenviews as events of name screen_view
, so we need to create a trigger for that event.
- Name your trigger
Screenview Trigger
- Trigger Type should be set to Custom
- Trigger should fire on Some Events
- For the Event, the Event Name should be equal to
screen_view
Create the custom function Tag
In Google Tag Manager, a Tag is what configures what happens when some trigger is activated. It will be automatically linked to a custom class which we will create in a later step.
The Tag Type should be a Function Call. You will also need to fill in the Class Name field with a class name of your liking, typically related to what the tag is about (here we chose ContentsquareScreenviewTag
), which you will re-use later.
Adding the trigger and the variables to the Tag
Edit the ContentsquareScreenviewTag
Tag:
- Add an argument with the key
auto_screen_name
and the variable{{Automatic Screenview Name}}
as the value - Add an argument with the key
manual_screen_name
and the variables{{Manual Screenview Name}} | {{Is User Logged In}}
or{{Manual Screenview Name}}
as the value - Add Contentsquare Screenview Trigger as the Tag's trigger
It should look like this now:
Create the custom function Tag class
In your app code, create a class of the same name as the Class Name
you configured in the GTM interface, for example ContentsquareScreenviewTag
. This is how GTM connects the configuration and the actual class to trigger. This class is used to call the Contentsquare screenview tracking function as follows:
[tab] Swift
@objc(ContentsquareScreenviewTag)
final class ContentsquareScreenviewTag: NSObject, TAGCustomFunction {
@objc func execute(withParameters parameters: [AnyHashable : Any]!) -> NSObject! {
let autoScreenName = parameters[AnyHashable("auto_screen_name")] as? String
let manualScreenName = parameters[AnyHashable("manual_screen_name")] as? String
let screenName = manualScreenName ?? autoScreenName ?? "No screen name!"
Contentsquare.send(screenViewWithName: screenName)
return nil
}
}
[tab] Objective-C
@implementation ContentsquareScreenviewTag <TAGCustomFunction>
- (NSObject*)executeWithParameters:(NSDictionary*)parameters {
NSString *autoScreenName = [parameters objectForKey:@"auto_screen_name"];
NSString *manualScreenName = [parameters objectForKey:@"manual_screen_name"];
NSString *screenName = manualScreenName ? : (autoScreenName ? : @"No screen name!");
[Contentsquare sendWithScreenViewWithName:screenName];
return nil;
}
@end
[tabend]
Now, every time Firebase sends a screenview event, Contentsquare will send one as well.
Screenview events with Custom Variables
It is possible to send custom variables along with the screenview events.
To do so:
-
Follow Use Google Tag Manager > Screenview events
-
Add extra arguments to the
ContentsquareScreenviewTag
Tag as follows:- Key: the index of the custom variable
- Value: a string that will contain the custom variable name and value such as
CVAR_NAME : {{Event Name}}
It should look like this:
In the code of your application, edit the code of your class to handle the added custom variables:
[tab] Swift
@objc(ContentsquareScreenviewTag)
final class ContentsquareScreenviewTag: NSObject, TAGCustomFunction {
@objc func execute(withParameters parameters: [AnyHashable : Any]!) -> NSObject! {
let autoScreenName = parameters[AnyHashable("auto_screen_name")] as? String
let manualScreenName = parameters[AnyHashable("manual_screen_name")] as? String
let screenName = manualScreenName ?? autoScreenName ?? "No screen name!"
var cvars = [CustomVar]()
for (gTagKey, gTagValue) in parameters {
guard let gTagKey = gTagKey as? String,
let cvarIndex = UInt32(gTagKey),
let gTagValue = gTagValue as? String else { continue }
let cvarKeyValue = gTagValue.components(separatedBy: ":")
if cvarKeyValue.count == 2 {
let name = cvarKeyValue[0].trimmingCharacters(in: .whitespaces)
let value = cvarKeyValue[1].trimmingCharacters(in: .whitespaces)
let cvar = CustomVar(index: cvarIndex, name: name, value: value)
cvars.append(cvar)
}
}
Contentsquare.send(screenViewWithName: screenName, cvars: [cvars])
return nil
}
}
[tab] Objective-C
@implementation ContentsquareScreenviewTag<TAGCustomFunction>
- (NSObject*)executeWithParameters:(NSDictionary*)parameters {
NSString *autoScreenName = parameters[@"auto_screen_name"];
NSString *manualScreenName = parameters[@"manual_screen_name"];
NSString *screenName = manualScreenName ?: autoScreenName ?: @"No screen name!";
NSMutableArray *cvars = [NSMutableArray array];
for (NSString *gTagKey in parameters) {
NSUInteger cvarIndex = [gTagKey integerValue];
NSString *gTagValue = parameters[gTagKey];
NSArray *cvarKeyValue = [gTagValue componentsSeparatedByString:@":"];
if (cvarKeyValue.count == 2) {
NSString *name = [cvarKeyValue[0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSString *value = [cvarKeyValue[1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
CustomVar *cvar = [[CustomVar alloc] initWithIndex:cvarIndex name:name value:value];
[cvars addObject:cvar];
}
}
[Contentsquare sendScreenViewWithName:screenName cvars:cvars];
return nil;
}
@end
[tabend]
With this code, the custom variables will be sent by Contentsquare automatically along with the screenview.
Transaction events
A Contentsquare transaction event can also be triggered for every Firebase ecommerce_purchase
event (See Firebase Objective-C/Swift documentation).
To do so, you will need to follow the setup described below.
Variables
We have to create at least two variables in order to be able to build a transaction event, Value and Currency. Optionally, it is also possible to pass a transaction identifier, meaning you would need to create an additional variable in order to be able to use it.
Value (mandatory)
Currency (mandatory)
Transaction ID (optional)
Trigger
Create the following custom trigger.
Tag
Create a Function call Tag called ContentsquareTransactionTag
, pass the two to three variables created earlier as arguments (skip the id
line if you do not want to use it) and set its trigger to the one you just created.
Implementing the function call
We need to create a ContentsquareTransactionTag
class, and have it adhere to the TAGCustomFunction
protocol to link it to the Google Tag Manager custom Tag of the same name.
Take good note that in order to be able to create and send a Contentsquare Transaction successfully, when you create a Firebase purchase event you will have to at least pass values for kFIRParameterValue
and kFIRParameterCurrency
, and can optionally pass a value for kFIRParameterTransactionID
, when you create a Firebase purchase event.
[tab] Swift
@objc(ContentsquareTransactionTag)
final class ContentsquareTransactionTag: NSObject, TAGCustomFunction {
@objc func execute(withParameters parameters: [AnyHashable : Any]!) -> NSObject! {
if let currency = parameters[AnyHashable("currency")] as? String,
let value = parameters[AnyHashable("value")] as? Float {
let identifier = parameters[AnyHashable("id")] as? String
Contentsquare.send(transaction: CustomerTransaction.init(id: identifier, value: value, currency: currency))
}
return nil
}
}
[tab] Objective-C
@implementation ContentsquareTransactionTag <TAGCustomFunction>
- (NSObject*)executeWithParameters:(NSDictionary*)parameters {
NSString *identifier = [parameters objectForKey:@"transaction_id"];
NSNumber *value = [parameters objectForKey:@"value"];
NSString * currency = [parameters objectForKey:@"currency"];
if (currency && value) {
[Contentsquare sendWithTransaction:[[CustomerTransaction alloc] initWithId:identifier value:[value floatValue] stringCurrency:currency]];
}
return nil;
}
@end
[tabend]
Now, every time Firebase sends an ecommerce_purchase
event, Contentsquare will send one as well.
Dynamic variable events
We can send values from any Firebase event as a Contentsquare dynamic variable:
Analytics.logEvent("addToCart", parameters: [
"product_count": 12,
"product_name": "ProductA"
])
Variables
The addToCart
event has parameters product_count
, product_name
, we will create variables called Product Count and Product Name with:
- The Variable Type set to Event Parameter
- The Event Type set to Custom Parameter
- The Event Parameter Key set manually to
product_count
/product_name
Check out Configure variables in Tag Manager for more information.
Triggers
We need to create a Trigger to tell Google Tag Manager when to send a Contentsquare dynamic variable, so we will create one for the event addToCart
.
- Name your trigger
AddToCartTrigger
- Trigger Type should be set to Custom
- Trigger should fire on Some Events
- For the Event, the Event Name should be equal to
addToCart
Create the custom function Tag
We need to create a function tag AddToCartDynamicVar
- The Tag Type set to Function Call
- The Class Name set to DynamicVarTag (we'll create this class later)
- Add two arguments,
Key: cs_dynamic_var_key, Value: Product
,Key: cs_dynamic_var_value, Value: {{Product Name}}: {{Product Count}}
- The Triggering set to AddToCartTrigger
It should look like this now:
Create the custom function Tag class
In your app code, create a class named DynamicVarTag
, assuming you used the name we suggested when creating the Tag in the GTM interface. If you named it otherwise, make sure to use the same name here, as it is how GTM connects the configuration and the actual class to trigger. We will use this class to call the Contentsquare dynamic variable function as follows.
import GoogleTagManager
import ContentsquareModule
@objc(DynamicVarTag)
final class DynamicVarTag: NSObject, TAGCustomFunction {
@objc func execute(withParameters parameters: [AnyHashable : Any]!) -> NSObject! {
guard let dynamicVarKey = parameters["cs_dynamic_var_key"] as? String else {
return nil
}
let dynamicVarValue = parameters["cs_dynamic_var_value"]
if let dynamicVarStringValue = dynamicVarValue as? String {
let dynamicVarString = DynamicVar(key: dynamicVarKey, value: dynamicVarStringValue)
Contentsquare.send(dynamicVar: dynamicVarString)
} else if let dynamicVarIntValue = dynamicVarValue as? UInt32 {
let dynamicVarInt = DynamicVar(key: dynamicVarKey, value: dynamicVarIntValue)
Contentsquare.send(dynamicVar: dynamicVarInt)
}
return nil
}
}
Now, every time Firebase sends a addToCart
event, Contentsquare will send a dynamic variable DynamicVar(key: "Product", value: "ProductA: 12")
.
Use Tealium
Our partner Tealium has developed a remote command module to integrate with the Contentsquare SDK. This solution leverages the convenience of iQ Tag Management to configure a native Contentsquare implementation without having to add Contentsquare-specific code to your app.
Follow instructions on Tealium's documentation: Remote Command for Contentsquare.
How the SDK works
Initialization
The way our SDK works is by auto-starting with the application launch and attaching to the current process in order to intercept the events and gestures we are interested in.
Configuration
Once started, our SDK fetches it config from our servers, and then depending on the segmentation size (defined by our team when you sign a contract) it will start collecting data from system and user events it detects from the runtime.
Tracking
The SDK monitors the application lifecycle events and the view hierarchy, and generates analytics data from the behavior of the app, the content of the screen and the interaction of the user. These events are then locally stored before they are sent to our servers. We then aggregate that data to create usable visual information into our Web Application, which you use to gather insights.
Sending data
Analytics data are sent in batches of maximum 50 events. Requests are triggered when network conditions allow for the server to be reached and:
- The current batch of events has reached 50
- Or the app is put in background
Our requests use lowPriority.
Session definition
A session represents a single period of user interaction in the app.
In Contentsquare, a session ends when the user has spent a certain amount of time outside the app. The SDK checks this when an app start
or app show
event is detected. This time spent outside the app, ending a session is set to 30 minutes by default. But it can be changed if it is requested.
If the app is put in the background or killed (intentionally by the user or by the OS) but the user comes back within 30 minutes, it will not end the session. These events are considered to be part of a session.
Collected data points
- The events are in JSON format, which lists the properties and their description in comment.
- Events are sent to the server in batches.
- A batch is a subset of events originating from one session. A session can send events in more than one batch.
- The batch size is defined in a run configuration file specific to each app (50 events by default).
Requests meta data
Each batch has a common header, a set of device specific, and repeatable information and an array of events, which is immutable. The header is composed at the moment of sending, so it has to be constant info ONLY. The array in the payload is data stored at the moment of collection.
The structure of the batches of events will have the following format:
{
pid:8, // int - ProjectId - represented in the mobile config format
uid:"ac1529a7-59f6-49d9-b254-6f0a22f57284", // String - uuid Unique user ID
dt:4, // int - device type (loosely enforced enum - [sdk-phone : 4, sdk-tablet :5])
dma:"Apple", // String - device manufacturer. This property is optional and if by any chance the device model cannot be determined it will be dropped from the Json object.
dmo:"iPhone X", // String - device model. This property is optional and if by any chance the device model cannot be determined it will be dropped from the Json object.
os:"13.1", // String - os version (iOS version name 12.1, 13, 13.1.1)
l:"en_US", // String - Language in iso-639 format https://www.ibabbleon.com/iOS-Language-Codes-ISO-639.html
tz:"Europe/Paris", // String - timezone https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
now:1522159618000, // long - timestamp when the batch was sent
to:{ // object - type origin of the event
an:"appname" // String - application name
st:"sdk-ios", // String - type of sdk
sf:"release", // string - sdk flavor/variant [release/debug/god]
},
r:{ // Device - resolution. Physical resolution on iOS.
w:1080, // int - width
h:1920 // int - height
d: 1.5 // The device density as float. Its values can vary between (0.75 and 10)
},
pl:[] // JSon array payload - list of event objects, see below
}
Events
Events meta data
All events have a common first part (don't confuse it with the header explained above). This common section is data which is common for all events by type but is gathered at the moment the event occurred. This common section is a part of the actual event payload.
{
upt:1522, // long - system update in milliseconds
euid:"uid", // String - event UUID
url:"app-and://identifier/mainPath/secondaryPath?title=screeName", // String - screenName is passed in the title query Parameter
scn:4, // int - the number of screens already shown in the session
c:3, // int - connectivity type [-1 - offline, 0 - error, 1 - wifi, 2 - 2g, 3 - 3g, 4 - 4g]
ci:"verizon", // String - carrier_id when user is on mobile network
o:1, // int - orientation [0 = portrait, 1 = landscape]
vo:{ // object - version origin of the event
sv:"1.1.0", // string version of the sdk
sb:4, // int - sdk build number
av:"appVersion", // String - application version
af:"appFlavor" // String - application string - [release/debug/god]
ab:1 // int - application build number
},
sn:1, // int - session id (positive int)
t:12894726459435 // long - timestamp of the event (in milliseconds)
}
All event specific properties are appended to this JSON object.
App Start
This event describes the absolute start of the app.
The trigger for this event is the absolute start of the app.
This is the first event sent when the SDK is invoked.
ea:0 // int - event action - defined above
App Show
This event is sent when the user brings the app in the foreground (switching from another app, exiting lock screen, etc.). This event means the app is focused.
ea:1 // int - event action - defined above
App Hide
This event is sent when the user exit (minimizes) the app, or switches to something else . This event means the app is not focused and the session might end, but we have not completely ended the session yet, as the user might return.
ea:2 // int - event action - defined above
Screenview
Everything starts with a View event. This is an event which describes the equivalent of a "page view" in the web. This event is sent when the Track Screen API is called.
cv
node is about Custom Variables being additional information on the screen, the user or the session, sent within a screenview.
They generally include datalayer information, such as: Screen type, Product category, if the user is signed in or not, the number of items in the cart...
This information enables user segmentation or screen grouping.
Custom variable is composed of three parts:
- An index: A unique integer to identify the custom variable across the app.
- A name: A description of what you want to track. Use the same name to identify occurrences of a same var.
- A value: A string describing the payload of the custom variable.
Custom variables number is limited to 20 by screen and cv
node won't be present if nothing has been declared by the client.
ea:4 // int - event action - defined above
st:"title", // String - screen title
sl:34543 // int - (load time) screen load time (diff between last action and this event)
cv:{
"2":{"name_1":"value_1"},
"5":{"name_2":"value_2"}
}
Tap
Single Finger gesture event, when the user is interacting with a loaded screen. This is an event which describes the equivalent of a "click" in the web. This event is defined by the following sequence of touch events:
- Touch Down -> N x Touch Move -> Touch Up
ea:6 // int - event action - defined above
tvp:"[root]>view#id>view2>", // String - target view path
tvid:"btn_ok", // String - target view id
ur: true, // boolean - was this a "responsive" touch event (the target view had a handler)
Long press
Single Finger gesture event, when the user is interacting with a loaded screen.
This event is defined by the following sequence of touch events:
- Touch Down → N x Touch Move → Touch Up
- Duration: > 500ms
- Distance: < 24 dp
ea:8 // int - event action - defined above
tvp:"[root]>view#id>view2>", // String - target view path
tvid:"btn_ok", // String - target view id
Drag (Slow Swipe)
Single Finger gesture event, when the user is interacting with a loaded screen.
This event is defined by the following sequence of touch events:
- Touch Down → N x Touch Move → Touch Up
- Distance: > 48 dp
- Finger Velocity < 100 dp/s
ea:9 // int - event action - defined above
tvp:"[root]>view#id>view2>", // String - target view path
tvid:"btn_ok", // String - target view id
fd: 3, // int - finger direction - [1,2,3,4,5] -> [up, down, left, right, complex_pattern]
tvd:100, // int - target view distance dragged
tvv:100 // int - target view velocity while dragging dp/s
Flick (Fast Swipe)
Single Finger gesture event, when the user is interacting with a loaded screen.
This event is defined by the following sequence of touch events:
- Touch Down → N x Touch Move → Touch Up
- Distance: > 48 dp
- Finger Velocity > 100 dp/s
ea:10 // int - event action - defined above
tvp:"[root]>view#id>view2>", // String - target view graph path
tvid:"btn_ok", // String - target view id
fd: 3, // int - finger direction - [1,2,3,4,5] -> [up, down, left, right, complex_pattern]
tvd:100, // int - target view distance scrolled
tvv:100 // int - target view velocity while scrolling dp/s
Transaction
To track transactions we provide a public API which can send a transaction object (see section Track Transactions). This object must contain the following parameters:
ea:16, // int - event action - defined above
tr:{ // a json object with different properties defining the transaction made
vl: 20.0, // mandatory - float - the value of the transaction
cu: 978, // mandatory - int - the ISO 4217 code of the currency of the transaction
id: "32akjkljklJ575775" // optional - string - an id for the transaction
}
Dynamic variables
To track dynamic variables we provide a public API (see section Dynamic variables).
// Dynamic variable event with a string value
ea:18, // int - event action - defined above
k:"key", // String - Custom key assigned by client.
v:"value" // String - Custom value assigned by client.
// Dynamic variable event with a number value
ea:19, // int - event action - defined above
k:"key", // String - Custom key assigned by client.
v: 2344 // Integer - Custom value assigned by client.
Reliable targets
In the Zoning Analysis module, reliable targets are assigned to zones to ensure that a UIView
remains accessible in the view hierarchy, even if it has been moved within the same page.
Having a reliable target for a view or a container of views enables the analysis of data across various Snapshots from different dates and versions, including layouts or different A/B testing variations.
One of the following properties must assigned for a view to have a reliable target.
Our SDK will check the properties in this order:
accessibility Identifier
if the view has accessibilityIdentifier
it uses it as first priority and provides a short reliable target.
Example:
/*
[SalesViewController]
ViewOne
|
viewTwo
/ | \
viewThree viewFour viewFive
*/
let viewOne = UIView()
viewOne.accessibilityIdentifier = "rootView"
let viewTwo = UIView()
viewTwo.restorationIdentifier = "dichotomy"
let viewThree = UIButton()
viewThree.accessibilityIdentifier = "on_sale"
let viewFour = UIButton()
viewFour.accessibilityIdentifier = "new_arrivals"
let viewFive = UIButton()
viewFive.accessibilityIdentifier = "for_kids"
viewTwo.addSubview(viewThree)
viewTwo.addSubview(viewFour)
viewTwo.addSubview(viewFive)
viewOne.addSubview(viewTwo)
// viewThree target would be: UIButton[ai:SalesViewController.on_sale]
// viewFour target would be: UIButton[ai:SalesViewController.new_arrivals]
// We change the view hierachy
viewThree.removeFromSuperview()
viewFour.addSubview(viewThree)
// viewThree target would be: UIButton[ai:SalesViewController.on_sale]
// viewFour target would be: UIButton[ai:SalesViewController.new_arrivals]
restoration Identifier
if the view has restorationIdentifier
and no accessibilityIdentifier
then it provides a long reliable target.
/*
[SalesViewController]
ViewOne
/
viewTwo
/ \
viewThree viewFour
*/
let viewOne = UIView()
viewOne.restorationIdentifier = "trichotomy"
let viewTwo = UIView()
viewTwo.restorationIdentifier = "dichotomy"
let viewThree = UIView()
let viewFour = UIView()
viewTwo.addSubview(viewThree)
viewTwo.addSubview(viewFour)
viewOne.addSubview(viewTwo)
// viewThree target would be: [root]>UIView[ri:SalesViewController.trichotomy]>UIView[ri:SalesViewController.dichotomy]>UIView:eq(0)
// viewFour target would be: [root]>UIView[ri:SalesViewController.trichotomy]>UIView[ri:SalesViewController.dichotomy]>UIView:eq(1)
tag
If the view has no accessibilityIdentifier
and no restorationIdentifier
then our SDK will use the tag
that has been set, and will provide a long reliable target as well.
tag yields the same identifiers as accessibility with a different tag
ex: [root]>UIView[tg:444]>UIView[tg:223]>UIView:eq(1)
Security
Transmission and hosting
Our servers use HTTPS to makes sure that data is encrypted in transport.
Compatibility
- Programming languages: The iOS SDK supports and tracks content on any screen developed in Objective-C or Swift (version >= 4.2).
- SwiftUI: is officially supported
- iOS version: starting with SDK 4.17.0 we support iOS 12.4 and later
- Current Xcode supports simulators on 12.4 and later
- ReactNative requires a minimum of iOS 12.4
- All devices released since 2014 can updated to iOS 12.4
SFSafariViewController
is not supported
Known limitations and recommendations
Snapshot fidelity: Combining Visual Effects with Filters
Symptom: Some elements of the snapshots may not be visible (e.g UITabBar buttons).
Explanation: Our capturing method prevents to capture views when Visual effects are combined with filters. Our tools captures as much as Xcode’s ‘Debug View Hierarchy’ feature. If you want to preview what is going to be rendered, you can use this feature to visualize your screen. Capturing a Snapshot of a UIVisualEffectView.
Gestures not attached to UIImageView
Symptom: When analysing the tap rate on an image, we see 0% when we know it is not possible.
Explanation: By default UIImageView
have their isUserInteractionEnabled
property set to false (See Apple documentation).
This means that the UIImageView
won’t receive any input event. All events will be handled by the first parent to have isUserInteractionEnabled
set to true (usually the first parent will do).
Workaround: Create a zone on the parent view to get the taps on the image.
ZONING LIMITATION IN SWIFTUI WITH VSTACK / LAZYVSTACK
The current snapshot and gesture tracking capabilities are limited in some cases when SwiftUI with VStack/LazyStack are used. Check the following Help Center article for more information about the symptom, root cause and potential mitigation solutions: I can't create a zone on a list element on iOS (SwiftUI)
Impact on performances
We always strive to be non-intrusive, and transparent to the developers of the client app. We apply this rule on the performance as well. These are the technical specifics we can share on performance, if you have any questions feel free to reach out to us.
The following performances results were obtained under the following conditions:
Condition | Value |
---|---|
Device model | iPhone 7 |
iOS version | 15.6.1 |
Test App built using Xcode version | 14.3.1 |
Test App built with Swift version | 5.8 |
We conducted the tests using a default Master-Detail
app built using AdHoc distribution with no app thinning and with Swift symbols stripped. In the app, the SDK was making calls to the Public APIs, running and collecting data in its default state.
Property | Value |
---|---|
SDK size (installed size, no Bitcode) | 4.6Mb |
Max RAM usage | <4Mb |
Max SDK CPU peak on event | <3% |
Data transmitted over network for a default batch size of 50 events The size of the batch can be customized if needed | ~29kb |
Troubleshooting
Requests are failing
In order for Contentsquare to work, you need to make sure the following endpoints are not blocked by your network (VPN):
Request | Endpoint | Detail |
---|---|---|
Config file | https://mobile-production.content-square.net | See Configuration |
Analytics data (EU) | https://m.csqtrk.net | See Sending data |
Analytics data (US) | https://m-aus1.contentsquare.net | See Sending data |
Session Replay data (EU) | https://ka-aeu1.contentsquare.net | See Session Replay requests |
Session Replay data (US) | https://ka-aus1.contentsquare.net | See Session Replay requests |
Snapshot (EU) | https://s.contentsquare.net | See Snapshot capture |
Snapshot (US) | https://s-aus1.contentsquare.net | See Snapshot capture |
Error message "Contentsquare SDK cannot execute operation"
If the following message appears in the logs:
CSLIB ℹ️ Info:⚠️ Contentsquare SDK cannot execute operation because it hasn't been started.
It means SDK API calls were done incorrectly.
In this case: gestures and lifecycle events won't be tracked by the SDK.
Two possible explanations exist for this issue:
- The SDK is manually started and API calls were made before call to
start
. - The SDK is automatically started and API calls were made before the
AppDelegate.willFinishLaunchingWithOptions
. Ensure that it doesn’t happen or contact our support team if you have a specific need.
Changelog
Related Links
📚 WebView Tracking Tag Documentation
📚 React Native Bridge Documentation
Third party libraries license
Core SDK
Libraries | License | Copyright |
---|---|---|
SwiftProtobuf | Apache-2.0 license | Copyright (c) 2014 - 2017 Apple Inc. and the project authors |
Error Analysis SDK
Libraries | License | Copyright |
---|---|---|
PLCrashReporter | MIT | Copyright (c) Microsoft Corporation. and the project authors |