Countly Documentation

Countly Resources

Here you'll find comprehensive guides to help you start working with Countly as quickly as possible.

iOS, watchOS, tvOS & macOS

This document includes necessary information for integrating Countly iOS / watchOS / tvOS / macOS SDK in your application.

Supported System Versions

Countly iOS SDK supports minimum Deployment Target iOS 8.0 (watchOS 2.0, tvOS 9.0, macOS 10.10) , and requires Xcode 9.0+ with Base SDK iOS 10.0+


To integrate Countly iOS SDK into your application, please follow these steps:

1. Download Countly iOS SDK (or clone it in your project as a git submodule, or use CocoaPods).

2. Add all files in countly-ios-sdk to your project on Xcode.

You can delete and from your project or remove them from Target > Build Phases > Compile Sources to avoid compiler warnings.

3. In your application delegate, import Countly.h ,
and inside application:didFinishLaunchingWithOptions: add following lines at the beginning:

#import "Countly.h"

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    CountlyConfig* config =;
    config.appKey = @"YOUR_APP_KEY"; = @"https://YOUR_COUNTLY_SERVER";
    [Countly.sharedInstance startWithConfig:config];

    // your code
    return YES;
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
    let config: CountlyConfig = CountlyConfig()
    config.appKey = "YOUR_APP_KEY" = "https://YOUR_COUNTLY_SERVER"
    Countly.sharedInstance().start(with: config)
    // your code

    return true

4. Set your app key and host on CountlyConfig object. Make sure you use App Key (under Management > Applications), not API Key or App ID.

If you are using Countly Enterprise Edition trial servers host should be, or Basically the domain you are accessing your trial dashboard from.

5. You can run your project and see first session data immediately on your Countly Server dashboard.

Advanced Configuration

Countly Code Generator

Countly Code Generator can be used to generate Countly iOS SDK code snippets easily and fast. You can provide values for your custom events, user profiles, or just start with basic integration. It will generate necessary code for you.

Debug Mode

If you want to enable Countly iOS SDK debug mode which logs internal infos, errors and warnings into console, you can set enableDebug flag on CountlyConfig object before starting Countly.

config.enableDebug = YES;
config.enableDebug = true

Additional Features

If you want to use additional features like PushNotifications, CrashReporting and AutoViewTracking you can specify them in features array on CountlyConfig object before you start:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    CountlyConfig* config =;
    config.appKey = @"YOUR_APP_KEY"; = @"https://YOUR_COUNTLY_SERVER";
    //You can specify additional features you want here
    config.features = @[CLYPushNotifications, CLYCrashReporting, CLYAutoViewTracking];
    [Countly.sharedInstance startWithConfig:config];

    // your code
    return YES;
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
    let config: CountlyConfig = CountlyConfig()
    config.appKey = "YOUR_APP_KEY" = "https://YOUR_COUNTLY_SERVER"

    //You can specify additional features you want here
    config.features = [CLYPushNotifications, CLYCrashReporting, CLYAutoViewTracking]

    Countly.sharedInstance().start(with: config)
    // your code

    return true

Device ID

You can configure device ID using CountlyConfig object and helper methods :

Using a Custom Device ID

If you want to use custom device ID or other system generated device IDs like IDFV, IDFA or OpenUDID, you can set deviceID property on CountlyConfig object. If deviceID property is not set explicitly, IDFV will be used by default.

config.deviceID = @"customDeviceID";  //Optional custom device ID
config.deviceID = CLYIDFV;            //Identifier for Vendor will be used as device ID (default)
config.deviceID = CLYIDFA;            //Identifier for Advertising will be used as device ID (Deprecated)
config.deviceID = CLYOpenUDID;        //OpenUDID will be used as device ID (Deprecated)
config.deviceID = "customDeviceID"  //Optional custom device ID
config.deviceID = CLYIDFV           //Identifier for Vendor will be used as device ID (default)
config.deviceID = CLYIDFA           //Identifier for Advertising will be used as device ID (Deprecated)
config.deviceID = CLYOpenUDID       //OpenUDID will be used as device ID (Deprecated)

Note: Once set, device ID will be persistently stored in device on the first launch, and will not change even after app delete and re-install, unless you change it explicitly.

Changing Device ID

You can use setNewDeviceID:onServer: method to change device id on runtime after you start Countly. You can either let the device counted as a new device or merge existing old device ID data on server.

If onServer bool is set, old device ID on server will be replaced with the new one, and data associated with old device ID will be merged automatically. You can pass either custom device ID or one of the predefined system device IDs.

[Countly.sharedInstance setNewDeviceID:@"new_device_id" onServer:YES];   //replace and merge on server
Countly.sharedInstance().setNewDeviceID("new_device_id", onServer:true)  //replace and merge on server

Otherwise, if onServer bool is not set, device will be counted as a new device on server.

[Countly.sharedInstance setNewDeviceID:CLYIDFV onServer:NO]; //no replace and merge on server, device will be counted as new
Countly.sharedInstance().setNewDeviceID(CLYIDFV, onServer:true)  //no replace and merge on server, device will be counted as new

Note: On macOS targets, there are no system device ID options, except legacy OpenUDID. To switch back to default system device ID, you can simply pass nil or an empty string "".

Handling User Login and Logout

If your app allows users to login, logged in users can be tracked with custom user ID (like accountID, username, memberID, email etc.) instead of device ID. For these cases, you can use userLoggedIn: and userLoggedOut convenience methods for changing device ID.

userLoggedIn: method handles switching from device ID to custom user ID for logged in users.
It is just a convenience method that sets passed user ID as new device ID and merging existing data on server.

[Countly.sharedInstance userLoggedIn:@"UserID"];

userLoggedOut method handles switching from custom user ID to device ID for logged out users. It is just a convenience method that handles resetting device ID to default one and starting a new session. Logged out user's data can will be tracked using default device ID henceforth.

[Countly.sharedInstance userLoggedOut];

Forcing Device ID Initialization

On the first app launch, Countly iOS SDK initializes device ID as specified in CountlyConfig object deviceID property, and stores it persistently. After this point, even if you delete and re-install the app Countly iOS SDK will continue to use initially stored device ID, to track app re-installs. So, while developing if you set deviceID property to something else on consecutive app launches, it will have no effect. In this case, you can set forceDeviceIDInitialization flag on CountlyConfig object, to force device ID initialization again. This will reset the initially stored device ID and Countly iOS SDK will work as if it is the first app launch.

config.forceDeviceIDInitialization = YES;
config.forceDeviceIDInitialization = true

After you start Countly with forceDeviceIDInitialization flag only once while developing, you can remove that line. forceDeviceIDInitialization flag is not meant for production, it is only for debugging purposes while developing.


On CountlyConfig object you can specify extra security features:

Pinned Certificates

You can use optional pinnedCertificates on CountlyConfig object for specifying bundled certificates to be used for public key pinning. Certificates from your Countly Server have to be DER encoded and should have .der, .cer or .crt extension. They also have to be added to your project and included in Copy Bundles Resources.

config.pinnedCertificates = @[@"mycertificate.cer"];
config.pinnedCertificates = ["mycertificate.cer"]

Custom Header Field

You can set optional customHeaderFieldName to be sent with every request. It is useful if your server requires special headers to be sent for security reasons. Every request sent to Countly Server will have this custom HTTP header and its value will be what you specify for customHeaderFieldValue property.

config.customHeaderFieldName = @"X-My-Custom-Field";
config.customHeaderFieldValue = @"my_custom_value";
config.customHeaderFieldName = "X-My-Custom-Field"
config.customHeaderFieldValue = "my_custom_value"

If you do not set customHeaderFieldValue value while you set customHeaderFieldName on initial Countly configuration (incase value is not available on app launch and will be retrieved later), requests will not start until you set it using setCustomHeaderFieldValue: method later.

[Countly.sharedInstance setCustomHeaderFieldValue:@"my_custom_value"];

Parameter Tampering Protection

You can set optional secretSalt to be used for calculating checksum of request data, which will be sent with each request using &checksum256 field. You need to set exactly the same secretSalt on Countly Server. If secretSalt on Countly Server is set, all requests would be checked for validity of &checksum256 field before being processed.

config.secretSalt = @"mysecretsalt";
config.secretSalt = "mysecretsalt"

Other Settings

On CountlyConfig object you can specify further optional settings:

Update Session Period

You can specify updateSessionPeriod on CountlyConfig object before starting Countly. It is used for session updating and sending queued events to server periodically. If updateSessionPeriod is not set explicitly, it will be 60 seconds for iOS, tvOS & macOS, and 20 seconds for watchOS by default.

config.updateSessionPeriod = 300;
config.updateSessionPeriod = 300

Event Send Threshold

You can specify eventSendThreshold on CountlyConfig object before starting Countly. It is used to send events requests to server when number of recorded custom events reach it without waiting for next update session request. If eventSendThreshold is not set explicitly it will be 10 for iOS, tvOS & macOS, and 3 for watchOS by default.

config.eventSendThreshold = 5;
config.eventSendThreshold = 5

Stored Requests Limit

You can specify storedRequestsLimit on CountlyConfig object before starting Countly. It is used to limit number of request to be stored when there is a Countly Server connection problem. In case your Countly Server is down, queued request may reach excessive numbers, and it may cause problems with being delivered to server and stored on the device. To prevent this, Countly iOS SDK will only store requests up to storedRequestsLimit. If number of stored requests reach storedRequestsLimit, Countly iOS SDK will start to drop oldest request while storing the newest one instead. If storedRequestsLimit is not set explicitly it will be 1000 by default.

config.storedRequestsLimit = 5000;
config.storedRequestsLimit = 5000

Always use POST method

You can set alwaysUsePOST flag on CountlyConfig object before starting Countly. It is used for sending all requests using HTTP POST method regardless of the data size. If set, all requests will be sent using HTTP POST method. Otherwise; only the requests with a file upload or data size more than 2048 bytes will be sent using HTTP POST method.

config.alwaysUsePOST = YES;
config.alwaysUsePOST = true

Manual Session Handling

You can set manualSessionHandling flag on CountlyConfig object before starting Countly to handle sessions manually.

config.manualSessionHandling = YES;
config.manualSessionHandling = true

By default, Countly iOS SDK tracks sessions automatically and sends begin_session request on initialization, end_session request when the app goes to background, and begin_session request again when the app comes back to foreground. In addition to them, Countly iOS SDK automatically sends a periodical (60 sec by default) update session request while the app is in foreground.

If manualSessionHandling flag is set, Countly iOS SDK does not send these requests automatically. So, you need to call beginSession, updateSession and endSession methods manually after you start Countly, depending on your own definition of a session.

[Countly.sharedInstance beginSession];
[Countly.sharedInstance updateSession];
[Countly.sharedInstance endSession];

Note: Countly Servers older than v16.12 should receive at least 1 begin_sessionrequest to initialize user on server, before receiving events and other information. Otherwise, all the data sent to server without a previous begin_session request will be ignored.


You can set enableAttribution flag on CountlyConfig object to enable campaign attribution. If set, IDFA (Identifier For Advertising) will be sent with every begin_session request, unless user has limited ad tracking in iOS Settings.

config.enableAttribution = YES;
config.enableAttribution = true


Default Device ID

As of Countly iOS SDK v16.06.4+, IDFV will be used as default device ID instead of IDFA, since IDFA on iOS10+ returns zero for users switched on Limit Ad Tracking setting. Even if CLYIDFA is explicitly set as deviceID on CountlyConfig object, IDFV will be used if IDFA can not be retrieved.

Legacy OpenUDID Users

If you are currently using a Countly iOS SDK version older than v16.02 and upgrading; please be advised if you do not set deviceID to CLYOpenUDID on CountlyConfig object, all your existing users will be counted as new users. In order to prevent this, you need to set deviceID to CLYOpenUDID on CountlyConfig object before you start Countly.

config.deviceID = CLYOpenUDID;  //OpenUDID will be used as device ID
config.deviceID = CLYOpenUDID  //OpenUDID will be used as device ID

As we plan to drop support for OpenUDID in the next version, we strongly recommend using setNewDeviceID:onServer: method after starting Countly, to switch to another deviceID like CLYIDFV or your own.

[Countly.sharedInstance startWithConfig:config];
//after starting Countly
[Countly.sharedInstance setNewDeviceID:CLYIDFV onServer:YES];
Countly.sharedInstance().start(with: config)
//after starting Countly
Countly.sharedInstance().setNewDeviceID(CLYIDFV, onServer:true)

Zero-IDFA Fix

With the release of iOS10, IDFA (Identifier for Advertising) value is 00000000–0000–0000–0000–000000000000 for all users who switched Limit Ad Tracking setting on. If you are currently using a Countly iOS SDK version older than v16.06.4 and upgrading; you may need to apply Zero-IDFA fix for persistently stored device ID and queued requests. To apply fix, you need to set applyZeroIDFAFix flag on CountlyConfig object:

config.applyZeroIDFAFix = YES;
config.applyZeroIDFAFix = true

And make sure your server has IDFA Fix Plugin. For more technical details about the Zero-IDFA issue, please see our blog post.

Automatic Reference Counting (ARC)

Countly iOS SDK uses Automatic Reference Counting (ARC). If you are integrating Countly iOS SDK into a non-ARC project, you should add -fobjc-arc compiler flag to all Countly iOS SDK implementation (*.m) files, under Target > Build Phases > Compile Sources.

App Transport Security (ATS)

With App Transport Security introduced in iOS 9, connections do not follow some security requirements will fail. You can see these requirements here. If your Countly Server instance does not meet these requirements, you may need to add NSAppTransportSecurity key into your applications Info.plist file, with NSAllowsArbitraryLoads or NSExceptionDomains as value, to communicate with your Countly Server.

Swift Projects

For using Countly on Swift based projects please make sure your Bridging Header File is configured properly for each target. And then import Countly.h file in Bridging Header file. After that you can use Countly methods in your Swift projects seamlessly.

And for Notification Service Extension targets, just import CountlyNotificationService.h in Bridging Header file.

You can see details on how to create Bridging Header file here.

Updating Countly iOS SDK

Before upgrading to a new version of Countly iOS SDK, do not forget to remove the existing old files from your project first. And while adding new Countly iOS SDK files again, please make sure you choose targets correctly and select "Copy items if needed" checkbox.


You can integrate Countly iOS SDK using CocoaPods. For more information, please see Countly CocoaPods page. Please make sure you have the latest version of CocoaPods and your local spec repo is updated. For Notification Service Extension targets, please make sure your Podfile uses subspecs like this:

target 'MyMainApp' do
  platform :ios,'8.0'
  pod 'Countly'

target 'CountlyNSE' do
  platform :ios,'10.0'
  pod 'Countly/NotificationService'

For Swift projects please see:

App Store Connect IDFA Warning

As Countly iOS SDK source has references to IDFA and App Store Connect checks for API usage, even if you are not explicitly using IDFA as device ID, you may need to answer Yes for "Does this app use the Advertising Identifier (IDFA)?" question on App Store Connect app submit form. Please make sure you follow the instructions specified in iTunes Connect Developer Guide - The Advertising Identifier (IDFA) section. Otherwise your app may get rejected due to "Improper use of IDFA" or fail to proceed on app submitting. In screenshot below, you can see which checkboxes to select while sending your app to the App Store :

If you are using an advertisement system, you might need to check "Serve advertisements within the app" checkbox too.

If you do not want any IDFA references to be part of you app, you can add COUNTLY_EXCLUDE_IDFA=1 flag to Build Settings > Preprocessor Macros section in Xcode. Once this flag is added, IDFA references won't be a part of the final product. So, you can safely say No to IDFA usage question on iTunes Connect.


If you want to rebrand Countly iOS SDK or make it whitelabel, you can use this rebranding script:

Frequently Asked Questions (FAQ) Page

For frequently asked questions about Countly iOS SDK you can see the FAQ page:

Recording Events

Here is a quick summary on how to use custom events recording methods:

Regular Events

In examples below, we will be recording a event named purchase with different scenarios:

  • purchase event occurred 1 time
[Countly.sharedInstance recordEvent:@"purchase"];
  • purchase event occurred 3 times
[Countly.sharedInstance recordEvent:@"purchase" count:3];
Countly.sharedInstance().recordEvent("purchase", count:3)
  • purchase event occurred 1 times with the total amount 3.33
[Countly.sharedInstance recordEvent:@"purchase" sum:3.33];
Countly.sharedInstance().recordEvent("purchase", sum:3.33)
  • purchase event occurred 3 times with the total amount 9.99
[Countly.sharedInstance recordEvent:@"purchase" count:3 sum:9.99];
Countly.sharedInstance().recordEvent("purchase", count:3, sum:3.33)
  • purchase event occurred 1 time from country : Germany, on app_version : 1.0
NSDictionary* dict = @{@"country":@"Germany", @"app_version":@"1.0"};

[Countly.sharedInstance recordEvent:@"purchase" segmentation:dict];
let dict : Dictionary<String, String> = ["country":"Germany", "app_version":"1.0"]

Countly.sharedInstance().recordEvent("purchase", segmentation:dict)
  • purchase event occurred 2 times from country : Germany, on app_version : 1.0
NSDictionary* dict = @{@"country":@"Germany", @"app_version":@"1.0"};

[Countly.sharedInstance recordEvent:@"purchase" segmentation:dict count:2];
let dict : Dictionary<String, String> = ["country":"Germany", "app_version":"1.0"]

Countly.sharedInstance().recordEvent("purchase", segmentation:dict, count:2)
  • purchase event occurred 2 times with the total amount 6.66, from country : Germany, on app_version : 1.0
NSDictionary* dict = @{@"country":@"Germany", @"app_version":@"1.0"};

[Countly.sharedInstance recordEvent:@"purchase" segmentation:dict count:2 sum:6.66];
let dict : Dictionary<String, String> = ["country":"Germany", "app_version":"1.0"]

Countly.sharedInstance().recordEvent("purchase", segmentation:dict, count:2, sum:6.66)

Timed Events

In examples below, we will be recording a timed event called level24 to track how long it takes to complete:

  • level24 started
[Countly.sharedInstance startEvent:@"level24"];
  • level24 ended
[Countly.sharedInstance endEvent:@"level24"];

Additionally, you can provide more information like segmentation, count and sum while ending an event.

  • level24 ended 1 time with the total point of 34578, from country : Germany, on app_version : 1.0
NSDictionary* dict = @{@"country":@"Germany", @"app_version":@"1.0"};

[Countly.sharedInstance endEvent:@"level24" segmentation:dict count:1 sum:34578];
let dict : Dictionary<String, String> = ["country":"Germany", "app_version":"1.0"]

Countly.sharedInstance().endEvent("level24", segmentation:dict, count:1, sum:34578)

Duration of the event will be calculated automatically when endEvent method is called.

Or, you if you measure duration of an event yourself, you can directly record it like this:

  • level24 took 344 seconds to complete:
[Countly.sharedInstance recordEvent:@"level24" duration:344];
Countly.sharedInstance().recordEvent("level24", duration:344)

Additionally, you can provide more information like segmentation, count and sum as well.

  • level24 took 344 seconds to complete 2 times with the total point of 34578, from country : Germany, on app_version : 1.0
NSDictionary* dict = @{@"country":@"Germany", @"app_version":@"1.0"};

[Countly.sharedInstance recordEvent:@"level24" segmentation:dict count:2 sum:34578 duration:344];
let dict : Dictionary<String, String> = ["country":"Germany", "app_version":"1.0"]

Countly.sharedInstance().recordEvent("level24", segmentation:dict, count:2, sum:34578, duration:344)

Event Names and Segmentation

Event names must be non-zero length valid NSString and segmentation must be an NSDictionary which does not contain any custom objects, as it will be converted to JSON.

Push Notifications

Setting up APNs authentication

First you need to acquire Push credentials from Apple using one of the following methods:
A) APNs Auth Key (preferred)
B) Universal (Sandbox + Production) Certificate.

A) Getting APNs Auth Key

APNs Auth Key is the preferred way of authentication on APNs for a number of reasons including less issues during configuration and being able to reuse the same connection with multiple apps.

To get APNs Auth key, first go to Create a New Key section on Apple Developer website.

Check APNs option and create your key.
Then download it and store in a safe place, you won't be able to download it again.
To upload a key file to Countly you'll also need some identifiers:

  • Key ID (filled automatically if you kept original Auth Key filename, otherwise visible on key details panel)

  • Team ID (see Membership section)

  • Bundle ID (see App IDs section)

B) Getting APNs Universal (Sandbox + Production) Certificate

Please go to Certificates section of Certificates, Identifiers & Profiles page on Apple Developer website.
Click plus sign and select Apple Push Notification service SSL (Sandbox & Production) type.
Follow the instructions. Once you are done, download it and double click to add it to your Keychain.

Next, you'll need to export your push certificate in p12 format.
Please open Keychain Access app, select login keychain and My Certificates category.
Search for your app id and find the certificate starting with Apple Push Services.
Select both the certificate and its private key as in screenshot below.
Right click and choose Export 2 items... and save it.
You're free to name the p12 file as you wish and set up a passphrase or leave it empty.

Once you download your auth key or export your certificate, you need to upload it to your Countly Server.
Please go to Management > Applications > Your App
Click on Edit and upload your auth key or exported certificate under APN Credentials section.

After filling all the required fields, click Validate button. Countly will check validity of credentials by initiating a test connection to APNs. If validation succeeds, click Save changes.

Configuring iOS app

Using Countly Push Notifications in iOS apps is pretty straightforward. First, integrate Countly iOS SDK as usual if you still haven't.

Then, under Capabilities section of Xcode, please enable Push Notifications and Remote notifications Background Mode for your target, as in screen shot below:

Now start Countly in application:didFinishLaunchingWithOptions: method of your app with the following configuration. Do not forget to specify CLYPushNotifications in features array of CountlyConfig object. After that, you'll need to ask for user's permission for push notifications using askForNotificationPermission method of Countly, at any point in the app. Countly iOS SDK will handle the rest automatically. No need to call any other method for registering or when device token is generated or a push notification is received.

#import "Countly.h"

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    //Start Countly with CLYPushNotifications feature as follows
    CountlyConfig* config =;
    config.appKey = @"YOUR_APP_KEY"; = @"https://YOUR_COUNTLY_SERVER";
    config.features = @[CLYPushNotifications];
//  config.isTestDevice = YES;     //if it is a test device, mark it here
    [Countly.sharedInstance startWithConfig:config];

    //Ask for user's permission for Push Notifications (not necessarily here)
    //You can do this later at anypoint in the app after starting Countly
    [Countly.sharedInstance askForNotificationPermission];

    // your code

    return YES;
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
    //Start Countly with CLYPushNotifications feature as follows
    let config: CountlyConfig = CountlyConfig()
    config.appKey = "YOUR_APP_KEY" = "https://YOUR_COUNTLY_SERVER"
    config.features = [CLYPushNotifications]
//  config.isTestDevice = true     //if it is a test device, mark it here
    Countly.sharedInstance().start(with: config)

    //Ask for user's permission for Push Notifications (not necessarily here)
    //You can do this later at anypoint in the app after starting Countly

    // your code

    return true

Note: Make sure you codesign your application using an explicit Provisioning Profile specific to your app's bundleID with aps-environment key in it. You can get it from iOS Provisioning Profiles section of Apple Developer website. Be advised, wildcard (*) profiles or profiles without aps-environment key does not work with APNs, and your device can not get a push token.

Rich Push Notifications (iOS10+ only)

Rich push notifications let's you send image, video or audio attachments, as well as customized action buttons on iOS10+. You need to set up Notification Service Extension to use it.

While main project file is selected, please click Editor > Add Target... menu in Xcode, and add a Notification Service Extension target.

Using Product Name field name the Notification Service Extension target as you wish (for example: CountlyNSE). And make sure Team is selected also.

Note: If Xcode asks a question about activating the scheme for newly added Notification Service Extension target, you can choose Cancel.

Under Build Phases > Compile Sources section of newly added extension target, click on + sign.

Select CountlyNotificationService.m in the list.

Then find NotificationService.m file (NotificationService.swift in Swift projects) in extension target. It is a default template file added by Xcode automatically. Inside this file import CountlyNotificationService.h

#import "CountlyNotificationService.h"
// No need to import files for Swift projects

And add the following line at the end of didReceiveNotificationRequest:withContentHandler: method as shown below:

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
  	//delete existing template code, and add this line
    [CountlyNotificationService didReceiveNotificationRequest:request withContentHandler:contentHandler];
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void)
    self.contentHandler = contentHandler
    bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
  	//delete existing template code, and add this line
    CountlyNotificationService.didReceive(request, withContentHandler: contentHandler);

Note: Please make sure you configure App Transport Security setting in extension's Info.plist file also, just like the main application. Otherwise media attachments from non-https sources can not be loaded.

Note: Please make sure you check Deployment Target version of extension target is 10, not 10.3 (or whatever minor version Xcode set automatically). Otherwise users running iOS versions lower than Deployment Target value can not get rich push notifications.

How Push Notifications Work

When a push notification is received, Countly iOS SDK handles everything automatically.

First, it checks if notification payload has Countly specific dictionary (c key) and notification ID inside it (i key). If Countly specific dictionary is present, it processes the notification. Otherwise it does nothing. In both cases, Countly iOS SDK forwards the notification to default application delegate implementation for manual handling.

Processing of notification payload depends on iOS version, applications status (background or foreground) at the time of notification receiving, and notification payload's content.

If there is a media attachment or custom action buttons, Notification Service Extension handles all automatically. Users can see them by using 3D Touch or swiping on older devices.

When the app is not in foreground, it waits for the user's interaction (like tapping notification itself or one of the custom action buttons). After user's interaction, it automatically records a specific event indicating that user has opened the push notification. And if user tapped one of the custom action buttons, it also records another specific event with button index segmentation and redirects to specified URL for that action.

When the app is in foreground, it uses UNNotificationPresentationOptionAlert mode on iOS10+ to present a default notification banner. And it uses a system UIAlertController on older iOS version and records push opened event directly.

You can see the detailed flow in this chart (download the chart for a bigger view):


When you send a push notification with custom actions buttons, you can redirect users to any custom page or view in your app, by specifying deeplinks as custom actions button URLs. For this, you need to create URL scheme (e.g: myapp://) in your project first.

To do this, select your app target in Xcode and open Info tab. Then, open URL Types section by clicking horizontal arrow, and click plus + sign there.

Enter an identifier (preferably in reverse domain format) into Identifier field. And enter your app's URL scheme (without ://part) into URL Schemes field. Optionally you can set Icon. And you can leave Role field whatever it is by default. When you are done, you can confirm that new URL scheme is added to your app's Info.plist file. It should look like this:

After setting up URL scheme, you should add application:openURL:options: method to your app delegate:

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options
    //handle URL here to navigate to custom views

    return YES;
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool
    //handle URL here to navigate to custom views

    return true

If your app's deployment target is lower than iOS9, you should add application:openURL:sourceApplication:annotation: method instead:

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(id)annotation
    //handle URL here to navigate to custom views

    return YES;
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool
    //handle URL here to navigate to custom views

    return true

Then in this method you can check the passed url for custom view navigation, using scheme and host properties. For example, if you set custom action button URLs as countly://productA and countly://productB, you can use something similar to this snippet:

if ([url.scheme isEqualToString: @"countly"])
    if ([ isEqualToString: @"productA"])
        // present view controller for Product A;
    else if ([ isEqualToString: @"productB"])
        // present view controller for Product B;
   // or you can use host property directly
if (url.scheme == "countly") 
    if ( == "productA") 
        // present view controller for Product A;
    else if ( == "productB") 
        // present view controller for Product B;

    // or you can use host property directly

When users tap on custom action buttons, Countly iOS SDK will open the specified URLs with your app's scheme. Following this, related method you added to your app's delegate will be called.

Advanced Setup

Test Mode

For Development builds (where the project's build settings includes DEBUG preprocessor macro by default), your device will be marked as test device automatically. So, you can send push notifications to test devices by choosing Test Users radio button on Create Message screen of Countly Dashboard.
If you want to manually mark your device as a test device for Distribution builds like TestFlight or AdHoc, you can use isTestDevice flag on CountlyConfig object.

config.isTestDevice = YES;
config.isTestDevice = true

Disabling Alerts Shown by Notification

To disable automatically showing of messages by CLYPushNotifications feature while the app is in foreground, you can set doNotShowAlertForNotifications flag on CountlyConfig object. If set, no message will be displayed by using default system UI in the app, but push open event will be recorded automatically.

config.doNotShowAlertForNotifications = YES;
config.doNotShowAlertForNotifications = true

Handling Notifications Manually

If you want to do additional custom work when a push notification is received, all you need to do is implement default push related methods in your application delegate (e.g. AppDelegate.m). After finishing its internal work, Countly iOS SDK will forward push related method calls to default implementations on application delegate.

Please make sure you do not set UNUserNotificationCenter.currentNotificationCenter's delegate manually, as Countly iOS SDK will be acting as the delegate. Just directly add UNUserNotificationCenterDelegate methods to your application delegate class.

Inside push notification userInfo dictionary, you can find all necessary information under Countly Payload dictionary specified by c (kCountlyPNKeyCountlyPayload) key. Custom action buttons array is specified by b (kCountlyPNKeyButtons) key here. And each custom action button's title and action URL is specified by t (kCountlyPNKeyActionButtonTitle) and l (kCountlyPNKeyActionButtonURL) keys respectively. Here is an example Countly Push Notification Payload:

	aps: {
		alert: “message string” | {title: “title string”, body: “message string"}, // if present
		sound: “default”, // if present
		badge: 123, // if present
		content-available: 1, // if contentAvailable API parameter set to true
		mutable-content: 1, // if buttons or media present
	c: {,
		i: "message id string”,
		l: “http://message-wide-url”, // if present
		a: “”, // if present
		b: [ // if present
			{t: “Button 1 title”, l: “http://button.1.url”},
			{t: “Button 2 title”, l: “http://button.2.url”} // if present
	// any other data properties if present

You can create your own custom UI to show notification message and custom action buttons as you wish, along with URLs to redirect users when action is taken. Once users take action by clicking your custom buttons, you need to report this event manually using this method:

NSDictionary* userInfo;     // notification dictionary
NSInteger buttonIndex = 1;  // clicked button index
                            // 1 for first action button
                            // 2 for second action button
                            // 0 for default action
[Countly.sharedInstance recordActionForNotification:userInfo clickedButtonIndex:buttonIndex];
let userInfo : Dictionary<String, AnyObject> = Dictionary() // notification dictionary
let buttonIndex : Int = 1  // clicked button index
                           // 1 for first action button
                           // 2 for second action button
                           // 0 for default action

Countly.sharedInstance().recordAction(forNotification:userInfo, clickedButtonIndex:buttonIndex)

Sending Push Token Always

Thanks to Remote Notification Background Mode of iOS, it is possible to send silent push notifications to users who have not given notification permission. But Countly iOS SDK does not send push tokens to server by default, from users who have not given permission for notifications. You can change this by setting sendPushTokenAlways flag of CountlyConfig object. If set, push tokens from all users regardless of their notification permission status will be sent to Countly server and these users will be listed as possible recipients on Create Message screen of Countly Dashboard. As these users are not able to be notified by alert, sound or badge, be advised this is useful only for sending data via silent notifications.

config.sendPushTokenAlways = YES;
config.sendPushTokenAlways = true

Notification Permission with Preferred Types and Callback

As asking for user's permission for push notifications differ by iOS versions, Countly iOS SDK has a one-liner convenience method askForNotificationPermission to do this for both iOS10 and older versions. It simply asks for user's permission for all available notification types. But if you need to specify which notification types your app will use (alert, badge, sound) or if you need a callback to see user's response to permission dialog you can use askForNotificationPermissionWithOptions:completionHandler: method.

UNAuthorizationOptions authorizationOptions = UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert;

[Countly.sharedInstance askForNotificationPermissionWithOptions:authorizationOptions completionHandler:^(BOOL granted, NSError *error)
    NSLog(@"granted: %d", granted);
    NSLog(@"error: %@", error);
let authorizationOptions : UNAuthorizationOptions = [.badge, .alert, .sound]

Countly.sharedInstance().askForNotificationPermission(options: authorizationOptions, completionHandler:
{ (granted : Bool, error : Error?) in
    print("granted: \(granted)")
    print("error: \(error)")


Enterprise Edition Feature

This feature is only available with Enterprise Edition subscription.

Countly lets you send GeoLocation based push notifications to your users. By default, Countly Server uses geoip database to deduce user's location. But, if your app has better means of detecting location, you can send this information to Countly Server by using initial configuration properties or relevant methods.

Initial configuration properties can be set on CountlyConfig object, to be sent on SDK initialization. These are:

location a CLLocationCoordinate2D struct specifying latitude and longitude.
ISOCountryCode an NSString in ISO 3166-1 alpha-2 format country code.
city an NSString specifying city name.
IP an NSString specifying IP address in IPv4 or IPv6 format.

config.location = (CLLocationCoordinate2D){35.6895,139.6917}; = @"Tokyo";

config.ISOCountryCode = @"JP";

config.IP = @""
config.location = CLLocationCoordinate2D(latitude:35.6895, longitude: 139.6917) = "Tokyo"

config.ISOCountryCode = "JP"

config.IP = ""

GeoLocation info recording methods also can be called anytime after Countly iOS SDK started. Values recorded using these methods will override the values specified on initial configuration.

[Countly.sharedInstance recordLocation:(CLLocationCoordinate2D){35.6895,139.6917}];

[Countly.sharedInstance recordCity:@"Tokyo" andISOCountryCode:@"JP"];

[Countly.sharedInstance recordIP:@""];
Countly.sharedInstance().recordLocation(CLLocationCoordinate2D(latitude:33.6895, longitude:139.6917))

Countly.sharedInstance().recordCity("Tokyo", andISOCountryCode:"JP")


GeoLocation info can also be disabled:

[Countly.sharedInstance disableLocationInfo];

Once disabled, you can re-enable GeoLocation info by calling recordLocation: or recordCity:andISOCountryCode: or recordIP: method.

Crash Reporting

Automatic Crash Reporting

For Countly Crash Reporting, you'll need to specify CLYCrashReporting in features array on CountlyConfig object before starting Countly.

config.features = @[CLYCrashReporting];
config.features = [CLYCrashReporting]

With this feature, Countly iOS SDK will generate a crash report if your application crashes due to an exception, and send it to Countly Server for further inspection. If a crash report can not be delivered to server (e.g. no internet connection, unavailable server) at the time of crash, then Countly iOS SDK stores the crash report locally in order to try again later.

A crash report includes following information:

  • Exception Info:

    • Exception Name
    • Exception Description
    • Stack Trace
    • Binary Images
  • Device Static Info:

    • Device Type
    • Device Architecture
    • Resolution
    • Total RAM
    • Total Disk
  • Device Dynamic Info:

    • Used RAM
    • Used Disk
    • Battery Level
    • Connection Type
    • Device Orientation
  • OS Info:

    • OS Name
    • OS Version
    • OpenGL ES Version
    • Jailbrake State
  • App Info:

    • App Version
    • App Build Number
    • Executable Name
    • Time Since App Launch
    • Background State
  • Custom Info:

    • Crash logs recorded using recordCrashLog: method
    • Crash segmentation specified in crashSegmentation property

You can use recordCrashLog: method to get custom logs with the crash reports. Logs generated by recordCrashLog: method are stored in a non-persistent structure, and delivered to server only in case of a crash.

[Countly.sharedInstance recordCrashLog:@"This is a custom crash log."];
Countly.sharedInstance().recordCrashLog("This is a custom crash log.")

And you can set optional crashSegmentation dictionary on ConfigObject, if you want to use custom crash segmentation:

config.crashSegmentation = @{@"key":@"value"};
config.crashSegmentation = ["key":"value"]

Manually Handled Exceptions

You can records handled exception manually, besides automatically reported unhandled exceptions and crashes:

NSException* myException = [NSException exceptionWithName:@"MyException" reason:@"MyReason" userInfo:@{@"key":@"value"}];

[Countly.sharedInstance recordHandledException:myException];
let myException : NSException = NSException.init(name:NSExceptionName(rawValue: "MyException"), reason:"MyReason", userInfo:["key":"value"])


You can also manually pass stack trace at the time of handled exception:

NSException* myException = [NSException exceptionWithName:@"MyException" reason:@"MyReason" userInfo:@{@"key":@"value"}];

[Countly.sharedInstance recordHandledException:myException withStackTrace:[NSThread callStackSymbols]];
let myException : NSException = NSException.init(name:NSExceptionName(rawValue: "MyException"), reason:"MyReason", userInfo:["key":"value"])

Countly.sharedInstance().recordHandledException(myException, withStackTrace: Thread.callStackSymbols)


Enterprise Edition Feature

This feature is only available with Enterprise Edition subscription.

Symbolication is the process of converting stack trace memory addresses in crash reports, into human readable useful information like class/method names, file names and line numbers.

In order to symbolicate memory addresses, dSYM files for each build needs to be uploaded to Countly Server.

Automatic dSYM Uploading

For Automatic dSYM Uploading, you can use countly_dsym_uploader script in Countly iOS SDK.

For this, go to Build Phases section of your app target, and click on plus ( + ) icon on the top left, then choose New Run Script Phase in the list.

Then, add the following snippet:

COUNTLY_DSYM_UPLOADER=$(/usr/bin/find $SRCROOT -name "" | head -n 1)

And select checkbox Run script only when installing

Note: Do not forget to replace your server and app key.

By default Xcode will generate dSYM files for Release build configuration, and countly_dsym_uploader script will handle the uploading automatically. You can check for the result at Report Navigator in Xcode. If dSYM upload is completed successfully, you will see [Countly] dSYM upload succesfully completed. message.

If there are any errors while uploading dSYM file, you can also see these error messages in Report Navigator. Some of the possible error reasons are: dSYM file not being created due to build configurations, dSYM file being created at a non-default location, wrong App ID and/or Countly Server address, network unavailability.

Manual dSYM Uploading

In case of an error with Automatic dSYM Uploading, or just if you want to upload your dSYM files manually, you can use our guide for Manual dSYM Uploading here. You also need to use Manual dSYM Uploading if Bitcode is enabled while uploading your app to the iTunes Connect.

Bitcode Enabled Apps

If Bitcode is enabled in your project while uploading your app to the iTunes Connect, Apple re-compiles your app to optimize it for specific devices. When Apple re-compiles your app, a new dSYM file is generated for the new build, and the dSYM file on your machine will not work for symbolication. So, you need to get this new dSYM file manually, and then upload it to Countly Server. In order to get the new dSYM file, you can use iTunes Connect or Xcode.

Using iTunes Connect:

  1. Login to iTunes Connect.
  2. Go to Activity tab.
  3. Select your app's Version and Build
  4. Under General Information click on Download dSYM.
  5. If downloaded file does not have any extension add .zip and unarchive to see contents.

Using Xcode:

  1. Open Organizer in Xcode
  2. Go to Archives tab.
  3. Select your app on the left list and select the archive
  4. Click on Download dSYMs...
  5. Xcode inserts the downloaded .dSYM files in the selected archive

For more information about downloading dSYM files from Apple, please see Apple's documentation here.

After you get your dSYM file from Apple, you can use our Manual dSYM Uploading guide.

How to Use Symbolication

Once your dSYM file is uploaded to Countly Server, you can symbolicate your crash reports coming from that build, on Crashes panel of your Count Server.

A symbolicated stack trace of a crash report looks like this:

Before symbolication:

mahya                               0x000000010006e174 mahya + 156020
mahya                               0x000000010006d060 mahya + 151648
mahya                               0x000000010006ad34 mahya + 142644

After symbolication:

-[MHViewController countlyProductionTest] (in mahya) (MHViewController.m:620)
-[MHViewController transitionToMahya] (in mahya) (MHViewController.m:443)
-[MHViewController textFieldShouldReturn:] (in mahya) (MHViewController.m:210)

For more information about how to use Symbolication feature on Countly Server, please see our Symbolication documentation here.

User Profiles

Enterprise Edition Feature

This feature is only available with Enterprise Edition subscription.

You can see detailed user information under User Profiles section of Countly Dashboard by recording user details. You can record default and custom properties of user details like this:

//default properties = @"John Doe";
Countly.user.username = @"johndoe"; = @"";
Countly.user.birthYear = @1970;
Countly.user.organization = @"United Nations";
Countly.user.gender = @"M"; = @"+0123456789";

//profile photo
Countly.user.pictureURL = @"";
//or local image on the device
Countly.user.pictureLocalPath = localImagePath;

//custom properties
Countly.user.custom = @{@"testkey1":@"testvalue1",@"testkey2":@"testvalue2"};

[Countly.user save];
//default properties
Countly.user().name = "John Doe" as CountlyUserDetailsNullableString
Countly.user().username = "johndoe" as CountlyUserDetailsNullableString
Countly.user().email = "" as CountlyUserDetailsNullableString
Countly.user().birthYear = 1970 as CountlyUserDetailsNullableNumber
Countly.user().organization = "United Nations" as CountlyUserDetailsNullableString
Countly.user().gender = "M" as CountlyUserDetailsNullableString
Countly.user().phone = "+0123456789" as CountlyUserDetailsNullableString

//profile photo
Countly.user().pictureURL = "" as CountlyUserDetailsNullableString
//or local image on the device
Countly.user().pictureLocalPath = localImagePath as CountlyUserDetailsNullableString

//custom properties
Countly.user().custom = ["testkey1":"testvalue1","testkey2":"testvalue2"] as CountlyUserDetailsNullableDictionary


In addition, you can use custom user details modifiers like this:

[Countly.user set:@"key101" value:@"value101"];
[Countly.user setOnce:@"key101" value:@"value101"];
[Countly.user unSet:@"key101"];

[Countly.user increment:@"key102"];
[Countly.user incrementBy:@"key102" value:5];
[Countly.user multiply:@"key102" value:2];

[Countly.user max:@"key102" value:30];
[Countly.user min:@"key102" value:20];

[Countly.user push:@"key103" value:@"singlevalue"];
[Countly.user push:@"key103" values:@[@"a",@"b",@"c",@"d"]];
[Countly.user pull:@"key103" value:@"b"];
[Countly.user pull:@"key103" values:@[@"a",@"d"]];

[Countly.user pushUnique:@"key104" value:@"uniqueValue"];
[Countly.user pushUnique:@"key104" values:@[@"uniqueValue2",@"uniqueValue3"]];

[Countly.user save];
Countly.user().set("key101", value:"value101")
Countly.user().setOnce("key101", value:"value101")

Countly.user().increment(by:"key102", value:5)
Countly.user().multiply("key102", value:2)

Countly.user().max("key102", value:30)
Countly.user().min("key102", value:20)

Countly.user().push("key103", value:"singlevalue")
Countly.user().push("key103", values:["a","b","c","d"])
Countly.user().pull("key103", value:"b")
Countly.user().pull("key103", values:["a","d"])

Countly.user().pushUnique("key104", value:"uniqueValue")
Countly.user().pushUnique("key104", values:["uniqueValue2","uniqueValue3"])


View Tracking

Auto View Tracking

For Countly Auto View Tracking, you'll need to specify CLYAutoViewTracking in features array on CountlyConfig object before starting Countly.

config.features = @[CLYAutoViewTracking];
config.features = [CLYAutoViewTracking]

After this point Countly iOS SDK will automatically track appeared and disappeared views. It simply intercepts viewDidAppear: method of UIViewController class and reports which view is displayed with view's name and duration. If view controller's title property is set, reported view's name will be the value of title property, otherwise view controller's class name.

You can temporarily enable or disable Auto View Tracking using 'isAutoViewTrackingActive' property.

Countly.sharedInstance.isAutoViewTrackingActive = NO;
Countly.sharedInstance.isAutoViewTrackingActive = false

If Auto View Tracking feature is not enabled on initial configuration, enabling or disabling this property later has no effect. It will always be disabled.

Exception View Controllers

By default, following system view controllers will be excluded from auto tracking, as they are not visible views but structural controllers:


In addition to these default exceptions, you can manually add your own exception view controllers using addExceptionForAutoViewTracking: method by passing view controller class name or title:

[Countly.sharedInstance addExceptionForAutoViewTracking:NSStringFromClass(MyViewController.class)];
[Countly.sharedInstance addExceptionForAutoViewTracking:@"MyViewControllerTitle"];
Countly.sharedInstance().addException(forAutoViewTracking:String.init(utf8String: object_getClassName(MyViewController.self))!)



Added view controller class name or titles will be ignored by Auto View Tracking and their appearances and disappearances will not be reported. Adding an already added view controller class name or title again will have no effect.

As well, you can remove manually added exception view controllers using removeExceptionForAutoViewTracking method by passing view controller class name or title:

[Countly.sharedInstance removeExceptionForAutoViewTracking:NSStringFromClass(MyViewController.class)];
[Countly.sharedInstance removeExceptionForAutoViewTracking:@"MyViewControllerTitle"];
Countly.sharedInstance().removeException(forAutoViewTracking:String.init(utf8String: object_getClassName(MyViewController.self))!)



Removing an already removed (or not yet added) view controller class name or title again will have no effect.

Manual View Tracking

In addition to Auto View Tracking, you can manually report appearance of a view using recordView: method with views name:

[Countly.sharedInstance recordView:@"MyView"];

When you report another view, duration of previous view will be calculated and view tracking event will be recorded automatically.


Star Rating

You can optionally set Countly iOS SDK to automatically ask users for a 1-to-5 star-rating, depending on app launch count for each version. For this, you need to set starRatingSessionCount property on CountlyConfig object. When total number of sessions reaches starRatingSessionCount, an alert view asking for 1-to-5 star-rating will be displayed automatically, once for each new version of the app.

config.starRatingSessionCount = 10;
config.starRatingSessionCount = 10

If you want star-rating dialog to be displayed only once for app lifetime, instead of for each new version, you can set starRatingDisableAskingForEachAppVersion flag on CountlyConfig object.

config.starRatingDisableAskingForEachAppVersion = YES;
config.starRatingDisableAskingForEachAppVersion = true

Additionally, you can customize star-rating dialog message using starRatingMessage property on CountlyConfig object. If you do not specify this property explicitly, message will be "How would you rate the app?" or corresponding localized version depending on device language. Currently supported localizations are: English, Turkish, Japanese, Chinese, Russian, Czech, Latvian and Bengali.

config.starRatingMessage = @"Please rate our app?";
config.starRatingMessage = "Please rate our app?"
config.starRatingDismissButtonTitle = "No, thanks."

Additionally, you can set starRatingCompletion block property on CountlyConfig object to be executed after star-rating dialog is shown automatically. Completion block has a single NSInteger parameter that indicates 1 to 5 star-rating given by user. If user dismissed dialog without giving a rating, this value will be 0 and it will not be reported to server.

config.starRatingCompletion = ^(NSInteger rating)
    NSLog(@"rating %d",(int)rating);
config.starRatingCompletion = { (rating : Int) in print("rating \(rating)") }

Additionally, you can use askForStarRating: method to ask for a star-rating anytime you want. It displays the 1-to-5 star-rating dialog manually and executes completion block after user's action. Completion block takes a single NSInteger parameter that indicates 1 to 5 star-rating given by user. If user dismissed dialog without giving a rating, this value will be 0 and it will not be reported to server. Manually asking for star-rating does not effect automatically asking of star-rating.

[Countly.sharedInstance askForStarRating:^(NSInteger rating)
    NSLog(@"rating %li",(long)rating);
Countly.sharedInstance().ask(forStarRating:{ (rating : Int) in print("rating \(rating)") })

Feedback Widgets

You can use Countly iOS SDK to display feedback widgets configured on Countly Server.
Once you call presentFeedbackWidgetWithID:completionHandler: method, feedback widget with given ID will be displayed in a WKWebView placed in a UIViewController.

First, the availability of the feedback widget will be checked asynchronously. If the feedback widget is available, it will be modally presented. Otherwise, completionHandler will be called with an NSError. completionHandler will also be called with nil when feedback widget is dismissed by user.

[Countly.sharedInstance presentFeedbackWidgetWithID:@"FEEDBACK_WIDGET_ID" completionHandler:^(NSError* error)
    if (error)
        NSLog(@"Feedback widget presentation failed: \n%@\n%@", error.localizedDescription, error.userInfo);
        NSLog(@"Feedback widget presented successfully");
Countly.sharedInstance().presentFeedbackWidget(withID: "FEEDBACK_WIDGET_ID", completionHandler:
{ (error : Error?) in
    if (error != nil)
        print("Feedback widget presentation failed: \n \(error!.localizedDescription) \n \((error! as NSError).userInfo)")
        print("Feedback widget presented successfully");

watchOS Integration

Providing a better Apple Watch experience for applications will be as important as the experience on the iPhone itself. “Offers Apple Watch App” statement placed at the top of the app pages on the App Store can be considered a sign showing importance of apps with Apple Watch support.

Just like iPhones and iPads, collecting and analyzing usage statistics and analytics data from Apple Watch is the key for offering a better experience. Fortunately, Countly iOS SDK has watchOS support. Here you can see how to use Countly iOS SDK in your watchOS apps:

1. First open a new or your existing Xcode project and add a new target by clicking + icon at the bottom of Projects and Targets List . (You can skip to step 4 if your project already has a Watch App, or you can visit for more information)

2. Select target template WatchKit App under watchOS > Application section. Do not choose WatchKit App for watchOS 1. In order to keep things simple, do not include Notification scene, Glance scene or Complication.

3. If Xcode asks if you want to activate WatchKit App scheme, click Activate.

4. Now it is time to add Countly iOS SDK to your project.
After cloning the Countly iOS SDK anywhere you want Drag&Drop countly-sdk-ios folder into your Xcode project and in the following dialog please make sure iPhone app target and WatchKit Extension target (not WatchKit App) are selected, as well as Copy items if needed checkbox.

5. Import Countly.h in ExtensionDelegate.m

#import "Countly.h"
// No need to import files for Swift projects

6. Add Countly starting code into applicationDidFinishLaunching method ExtensionDelegate.m

CountlyConfig* config =;
config.appKey = @"YOUR_APP_KEY"; = @"https://YOUR_COUNTLY_SERVER";
config.enableAppleWatch = YES;
[Countly.sharedInstance startWithConfig:config];
let config: CountlyConfig = CountlyConfig()
config.appKey = "YOUR_APP_KEY" = "https://YOUR_COUNTLY_SERVER"
config.enableAppleWatch = true
Countly.sharedInstance().start(with: config)

7. Add suspend code into applicationWillResignActive method of ExtensionDelegate.m

[Countly.sharedInstance suspend];

8. Add resume code into applicationDidBecomeActive method of ExtensionDelegate.m

[Countly.sharedInstance resume];

9. After adding these three lines of code into related extension delegate methods, try building the project. Everything should be OK now. If you run the watchOS app, you can see the session on your Countly dashboard.

Now you are ready to track your watchOS app with Countly.

By the way, session concept on watchOS is a little bit different than the one on the iOS, as watchOS apps are intended for brief user interaction. So, there are two values you might need to adjust depending on your watch apps use cases.

  • First one is updateSessionPeriod. Its default value is 20 seconds for watchOS while 60 seconds for iOS. This value determines how often session updating requests will be sent to server while the app is in use.
  • Second one is eventSendThreshold which is 3 for watchOS and 10 for iOS by default. Countly iOS SDK waits for the number of recorded unique events to reach this threshold, to deliver them to server until next session updating kicks in. Considering that Apple Watch is designed to be used for short sessions, these values seems proper in general. But you can change them depending on your watchOS app’s scenario.
config.updateSessionPeriod = 15;
config.eventSendThreshold = 1;
config.updateSessionPeriod = 15
config.eventSendThreshold = 1

And do not forget to set enableAppleWatch flag of CountlyConfig object on your watch apps iOS counterpart:

config.enableAppleWatch = YES;
config.enableAppleWatch = true

With this setting, iOS counterpart of your watchOS app will be reporting paired Apple Watch's information to server as well.

Note: Please make sure you do not set WCSession.defaultSession's delegate manually, as Countly iOS SDK will be acting as the delegate.


For compatibility with data protection regulations such as GDPR, Countly iOS SDK allows developers to enable/disable any feature at any time, depending on user consent. Currently available features with consent control are:

CLYConsentSessions : sessions
CLYConsentEvents : events
CLYConsentUserDetails : users
CLYConsentCrashReporting : crashes
CLYConsentPushNotifications : push
CLYConsentLocation : location
CLYConsentViewTracking : views
CLYConsentAttribution : attribution
CLYConsentStarRating : star-rating
CLYConsentAppleWatch : accessory-devices

To utilize consents, you should set requiresConsent flag is set on initial configuration.

config.requiresConsent = YES;
config.requiresConsent = true

With this flag set, Countly iOS SDK will not collect or send any data automatically, as well as ignoring all manual calls.
Until explicit consent is given for a feature, it will be inactive. After giving consent for a feature, it will be started immediately and kept active henceforth.

To give consent for a feature you can use giveConsentForFeature: method, passing the feature name:

[Countly.sharedInstance giveConsentForFeature:CLYConsentSessions];
[Countly.sharedInstance giveConsentForFeature:CLYConsentEvents];
Countly.sharedInstance().giveConsent(forFeature: CLYConsentSessions)
Countly.sharedInstance().giveConsent(forFeature: CLYConsentEvents)

Or, you can give consent for more than one feature at a time using giveConsentForFeatures: method, passing feature names as an NSArray:

[Countly.sharedInstance giveConsentForFeatures:@[CLYConsentSessions, CLYConsentEvents];
Countly.sharedInstance().giveConsent(forFeatures: [CLYConsentSessions, CLYConsentEvents])

Or, if you want to give consent for all the features, you can use giveConsentForAllFeatures convenience method:

[Countly.sharedInstance giveConsentForAllFeatures];

Countly iOS SDK does not persistently store status of given consents. You are expected to handle getting consent from end-users using proper UIs depending on your app's context, and storing them either locally or remotely. Following this, you need to call giving consent methods on each app launch, just after starting Countly iOS SDK, depending on the permissions you managed to get from the end-users.

If the end-user changes his/her mind about consents later, you need to reflect this to Countly iOS SDK, using cancelConsentForFeature: method:

[Countly.sharedInstance cancelConsentForFeature:CLYConsentSessions];
[Countly.sharedInstance cancelConsentForFeature:CLYConsentEvents];
Countly.sharedInstance().cancelConsent(forFeature: CLYConsentSessions)
Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)

Or, you can cancel consent for more than one feature at a time using cancelConsentForFeatures: method, passing feature names as an NSArray:

[Countly.sharedInstance cancelConsentForFeatures:@[CLYConsentSessions, CLYConsentEvents];
Countly.sharedInstance().cancelConsent(forFeatures: [CLYConsentSessions, CLYConsentEvents])

Or, if you want to cancel consent for all the features, you can use cancelConsentForAllFeatures convenience method:

[Countly.sharedInstance cancelConsentForAllFeatures];

Once consent for a feature is cancelled, that feature is stopped immediately and kept inactive henceforth.

Countly iOS SDK reports consent changes to Countly Server, so Countly Server can do preparations or clean-up on server side as well.