iOS SDK - Basic Integration


Before You Begin: SDK Prerequisites

Issues during integration? See FAQ below.

Follow the steps in Integrating a Singular SDK: Planning and Prerequisites.

These steps are prerequisites for any Singular SDK integration.


Install the SDK

You can install the Singular SDK using CocoaPods, Swift Package Manager, or a Static Library.

Installing the SDK Using CocoaPods
  1. Download and install the latest version of CocoaPods.
  2. To create a podfile, navigate to the project root folder in the Terminal and type:

    pod init
  3. To add the Singular SDK dependency, add the following to your project's Podfile:

    pod 'Singular-SDK'

    For example:

    pod_for_swiftcocoapods.png

  4. In your Terminal, navigate to the project root folder and run:

    pod install
  5. From this point forward, open the Xcode workspace file .xcworkspace to open the project, instead of the .xcodeproj file.
  6. Create a Swift bridging header according to the instructions below.
Installing the SDK Using Swift Package Manager
  1. In Xcode, go to File > Add Packages and enter the Singular SDK GitHub repository:

    https://github.com/singular-labs/Singular-iOS-SDK

    update_sdk_version_1.png

  2. Update the Singular SDK version:

    update_sdk_version_2.png

  3. Click the Add Package button.
  4. Go to Build Phases > Link Binary with Libraries and add the AdServices.framework library. Be sure to mark it as Optional since it's only available for devices with iOS 14.3 and higher.

    link_binary_adservices.png

  5. Go to Build Settings > Linking > Other Linker Flags and update Other Linker Flags with the following items:

    $(inherited) -ObjC -l"sqlite3.0" -l"z" -framework "AdSupport" -framework "Security" -framework "Singular" -framework "StoreKit" -framework "SystemConfiguration" -framework "WebKit" -framework "iAd"

    frameworks.png

    other_linker_flags.png

  6. Create a Swift bridging header according to the instructions below.
Installing the Singular SDK Framework (Static Library)

Downloading and installing the Singular Framework is only required if you are NOT using the CocoaPods or SPM methods above.

Are you upgrading from Singular SDK 12.3.2 or lower?

Follow the steps below to remove the old Static Library

  1. Navigate to the Project files and remove the Singular Static Library. Typically, you should have a folder named something like Singular-iOS-sdk-v12.3.2. Right-Click the folder and delete it from the project.
  2. Proceed to the Next section to install the new Framework

Adding the Singular Framework for the first time

  1. Download and unzip the SDK Framework.

    Select the correct Framework for your implementation:

  2. Add the unzipped folder to a folder in your Xcode project:

    In Xcode, right-click Your App Name > Add Files To [Your Project Name]. In the dialog that opens, select Options > Create Groups and add the folder where you unzipped the SDK.





    The Singular framework should now be in your project

  3. To add the required libraries:

    • In Xcode, select Build Phases > Link Binary With Libraries.
    • Click + and add the following libraries:

      Libsqlite3.0.tbd
      SystemConfiguration.framework
      Security.framework
      Libz.tbd
      AdSupport.framework
      WebKit.framework
      StoreKit.framework
      AdServices.framework (mark as Optional since it's only
          available for devices with iOS 14.3 and higher).
  4. Embed & Sign the Singular Framework
    • Navigate to General > Frameworks, Libraries, and Embedded Content
    • Adjust the Singular Framework to be "Embed & Sign"

Adding the Swift Bridging Header

If you installed the SDK using CocoaPods or the Swift Package Manager, you must create a Bridging Header for Swift to use the Obj-C libraries from the Singular SDK.

  1. In your project, create a new file of type Header, and name it YourProjectName-Bridging-Header.

    new_header_file.png

  2. Open the file and add the following:

    #import <Singular/Singular.h>

    For example:

    swift_cocoapods_import_singular.png

  3. Go to Build Settings > Objective-C Bridging Header and add the file's relative path:

    objective_c_bridging_header.png


Integrate the SDK

Important:

  • To use Swift, you must have a Bridging Header (see guide above).
  • If you added the Singular SDK using the Swift Package Manager, ensure you updated Build Settings > Other Linker Flags as explained above.

Importing the Singular Library

In the SceneDelegate, AppDelegate, or any file where Singular will be used, import the Singular class library to start using the Singular SDK.

// If installed with Cocoapods or Swift Package Manager

import Singular
          
// If installed manually in Objective-C (New Framework)

#import <Singular/Singular.h>

// If installed manually in Objective-C (Legacy)

#import "Singular.h"

Creating a Configuration Object

Before you initialize Singular functionality in your code, you have to create a Singular configuration object and set all your configuration options. This code block should be added in SceneDelegate (or if you are not using SceneDelegate, add it to AppDelegate).

The following code example creates a configuration object and sets some common configuration options, such as enabling SKAdNetwork in Managed Mode and setting a timeout to wait for an ATT response.

The following sections give more details about each of these options and how you can customize them.

Example: Creating a Configuration Object with Some Common Options

SwiftObjective-C
func getConfig() -> SingularConfig? {
     
     // Create the config object with the SDK Key and SDK Secret

     guard let config = SingularConfig(apiKey: 'APIKEY', andSecret: 'SECRET') else {
         return nil
         }
     
     // If you are using App Tracking Transparency:

     // Set a 300 sec delay before initialization to wait for 

     // the user's ATT response.

     // (Remove this if you are not displaying an ATT prompt!)

     config.waitForTrackingAuthorizationWithTimeoutInterval = 300
     
     // Support custom ESP domains

     config.espDomains = ["links.your-website-domain.com"]
     
     // Set a handler method for deep links

     config.singularLinksHandler = { params in
          self.handleDeeplink(params: params)
     }
     
     return config
      }

Note: Starting with Singular iOS SDK version 12.0.6, SKAdNetwork is enabled by default.

If you are still using an older version of the SDK, you need to enable SKAdNetwork using the following code when creating the configuration object:

SwiftObjective-C
// Enable SKAdNetwork in Managed Mode

config.skAdNetworkEnabled = true

Customizing SKAdNetwork Options

SKAdNetwork is Apple's framework for determining mobile install attribution without compromising the end user's privacy. SKAdNetwork lets you measure the performance of your app marketing campaigns without sharing the user's personally identifiable information.

By default, SKAdNetwork is enabled in Managed Mode, where the conversion value is managed directly by Singular from the server side. If using Managed Mode, you don't need to add any code to your app to handle SKAdNetwork.

This allows for maximum flexibility as you can set and change your conversion values through the Singular platform without modifying your client-side code.

This server-side managed mode also helps you deal with the SKAdNetwork timers. SKAdNetwork allows you to update the conversion value within 24 hours from the time of registration to SKAdNetwork. Any call to update the conversion value extends the timer by 24 more hours. Therefore, when choosing your conversion events, you'll have to make sure the events happen within that update window. In managed mode, you can change the conversion event configuration at any time without releasing a new version of your app.

Using SKAdNetwork in Manual Mode (Advanced)

Learn how to use SKAdNetwork in manual mode

If you want to update the conversion value on your own using the app code, you first have to set the manualSkanConversionManagement flag in the Singular Config. This lets you use several SDK methods to retrieve and update the conversion value manually.

To enable Manual Mode:

SwiftObjective-C
func getConfig() -> SingularConfig? {
     
     // Singular Config Options

     guard let config = SingularConfig(apiKey: Constants.APIKEY,
       andSecret: Constants.SECRET) else {
         return nil
         }
     
     //...
     config.manualSkanConversionManagement = true
     //...
     
     return config
}

To update the conversion value:

In Manual Mode, to update the conversion value, you need to use the skanUpdateConversionValue method. You can use it wherever needed in your app's lifecycle.

Note: The skanUpdateConversionValue method will not function if you have not enabled manualSkanConversionManagement.

skanUpdateConversionValue Method
Description Manually updates the SKAdNetwork conversion value.
Signature (BOOL)skanUpdateConversionValue:(NSInteger)conversionValue;
Usage Example
SwiftObjective-C
// Sending a Standard Event for Login

Singular.event(EVENT_SNG_LOGIN)
      
// Manually updating the conversion value to 7 after the Event

Singular.skanUpdateConversionValue(7)

Other SKAdNetwork methods:

To get the current conversion value, use the skanGetConversionValue method or conversionValueUpdatedCallback. Both work in Managed and Manual Mode.

skanGetConversionValue Method
Description Get the current conversion value tracked by the Singular SDK.
Signature (NSNumber *)skanGetConversionValue;
Usage Example
SwiftObjective-C
let conversionValue = Singular.skanGetConversionValue()
conversionValueUpdatedCallback Callback
Description Get the current conversion value tracked by the Singular SDK.
Signature void(^conversionValueUpdatedCallback)(NSInteger);
Usage Example
SwiftObjective-C
func getConfig() -> SingularConfig? {
     // Singular Config Options

     guard let config = SingularConfig(apiKey: Constants.APIKEY,
       andSecret: Constants.SECRET) else {         
         return nil
         }     
     //...
     config.conversionValueUpdatedCallback = { conversionValue in
     // Here you have access to the latest conversion value

     }
     //...
     return config
}

Handling ATT Consent (Setting an Initialization Delay)

Displaying an ATT (App Tracking Transparency) Prompt

Starting with iOS 14.5, apps are required to ask for user consent (using the App Tracking Transparency framework) before they can access and share some user data that is helpful for tracking purposes, including the device's IDFA.

Singular highly benefits from having the IDFA to identify devices and perform install attribution (although there are ways to perform attribution without the IDFA). We strongly recommend that you ask for the user's consent to get the IDFA.

Delaying Initialization to Wait for ATT Response

By default, the Singular SDK sends a user session when it's initialized. When a session is sent from a new device, it immediately triggers Singular's attribution process - which is performed based only on the data available to Singular at that point. Therefore, it's essential to ask for consent and retrieve the IDFA before the Singular SDK sends the first session.

To delay the firing of a user session, initialize the Singular SDK with the waitForTrackingAuthorizationWithTimeoutInterval option in the Config object. This option is already included in the code sample in 2.2. Creating a Configuration Object.

SwiftObjective-C
func getConfig() -> SingularConfig? {
     guard let config = SingularConfig(apiKey: Constants.APIKEY,
       andSecret: Constants.SECRET) else {
         return nil
         }
     //...
     config.waitForTrackingAuthorizationWithTimeoutInterval = 300
     //...
     return config
}

Tip: When you set an initialization delay, the app flow is as follows:

  1. When the app opens, the Singular SDK starts recording a session and user events but does not send them to the Singular server yet.
  2. When App Tracking Transparency consent is granted/denied, or the set time elapses, the SDK sends the session and any queued events to the Singular server (with or without the IDFA).
  3. Singular then starts the attribution process, taking advantage of the IDFA if it is available.
Learn About All Possible ATT Scenarios

The following table summarizes the possible scenarios using this integration:

Scenario IDFA Availability
The user sees the consent dialog and grants consent before the set time elapses. IDFA is available
The user sees the consent dialog and denies consent before the set time elapses. IDFA is not available
The set time expires, then the user is shown the consent dialog and grants consent. IDFA is available only for the user events that are reported after the consent is granted
The set time expires, then the user is shown the consent dialog and denies consent. IDFA is not available
The user is shown the consent dialog, exits the app without taking action, and later opens the app and grants consent after the set time has expired. Any queued events are sent to the Singular server when the app is reopened. The IDFA is not available for these events. Any events tracked after consent is granted do have IDFA associated with them.
The user is shown the consent dialog, exits the app without taking action, and later opens the app and denies consent. Any queued events are sent to the Singular servers when the app is reopened. The IDFA is not available for these events or any of the events tracked afterward.

Initializing the Singular SDK

Tip: Before proceeding, make sure you have completed the steps below!

  • Added the Singular Library
  • If using swift: created a Swift Bridging Header
  • Added code to create the Singular Config object
  • Added a deep link handler
  • Enabled SKAdNetwork
  • If showing the ATT: added waitForTrackingAuthorizationWithTimeoutInterval
  • Test built the app successfully (the app should build without error at this stage)

The Singular SDK should be initialized every time your app is opened. This is a prerequisite to all Singular attribution functionality, and it also sends a new user session to Singular (sessions are used to calculate user retention). The SDK is initialized using the config object that you created in Creating a Configuration Object.

Where to Add the Initialization Code?

You have to initialize the Singular SDK in every entry point to the app:

  • For iOS 13+ using the SwiftUI Interface with no SceneDelegate or AppDelegate, initialize the Singular SDK in the following ContentView().onOpenURL() and .onChange(of: scenePhase) (See code below for example).

  • For iOS 13+, initialize the Singular SDK in the following SceneDelegate functions: willConnectTo session, continue userActivity, openURLContexts URLContexts.

  • For older versions of iOS that don't support SceneDelegate, initialize the SDK in the following AppDelegate functions: didFinishLaunchingWithOptions, continueUserActivity, openURL.

Initialization Code Examples

For iOS 13+ (Swift SceneDelegate)
// INITIALIZE THE SDK IN THE FOLLOWING SCENEDELEGATE FUNCTIONS]

     
// willConnectTo session

func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
  options connectionOptions: UIScene.ConnectionOptions) {
    let userActivity = connectionOptions.userActivities.first
    // Print IDFV to Console for use in Singular SDK Console

    print(Date(), "-- Scene Delegate IDFV:", 
    UIDevice().identifierForVendor!.uuidString as Any)
    
    //Initialize the Singular SDK here:
    if let config = self.getConfig() {
      config.userActivity = userActivity
      Singular.start(config)
    }
}
      
// continue userActivity

      
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
  // Starts a new Singular session on continueUserActivity

  if let config = self.getConfig() {
    config.userActivity = userActivity
    Singular.start(config)
  }
}
      
//openURLContexts URLContexts
      
func scene(_ scene: UIScene, openURLContexts URLContexts: 
  Set<UIOpenURLContext>) {
    // Starts a new Singular session on cold start from deeplink scheme

    if let config = self.getConfig() {
      config.openUrl = openurlString
      Singular.start(config)
    }
    
    // Add custom code here to Redirect to non-Singular deep links

    //...
}
For iOS 13+ (SwiftUI Interface)
// INITIALIZE THE SDK IN THE FOLLOWING WINDOWGROUP FUNCTIONS

var body: some Scene {
    WindowGroup {
        ContentView()
            .onOpenURL(perform: { url in
                openURL = url
                // Initialize Singular from an openURL

                if let config = self.getConfig() {
                    config.openUrl = url
                    Singular.start(config)
                }
            })
    }
    .onChange(of: scenePhase) { oldValue, phase in
        // The SwiftUI ScenePhases replaces the old SceneDelegate lifecycle events

        switch phase {
        case .background:
            print("App Scene: backgrounded")
        case .inactive:
            print("App Scene: inactive")
        case .active:
            print("App Scene: active")
            
            // Initialize Singular

            if let config = self.getConfig() {
                Singular.start(config)
            }
        @unknown default:
            print("App Scene: unknown")
        }
    }
}
For iOS 13+ (Objective-C SceneDelegate)
// INITIALIZE THE SDK IN THE FOLLOWING SCENEDELEGATE FUNCTIONS

     
// willConnectToSession

- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session 
  options:(UISceneConnectionOptions *)connectionOptions {
    NSUserActivity* userActivity = [[[connectionOptions userActivities] allObjects] 
      firstObject];
    // Print identifier for Vendor (IDFV) to Xcode Console for use in Singular SDK Console

    NSLog(@"-- Scene Delegate IDFV: %@", [[[UIDevice currentDevice] identifierForVendor] UUIDString]);
    
    // Start a new Singular session from a backgrounded app

    SingularConfig *config = [self getConfig];
    config.userActivity = userActivity;
    [Singular start:config];
}
    
// continueUserActivity

- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity{
  // Starts a new Singular session from a backgrounded App

  SingularConfig *config = [self getConfig];
  config.userActivity = userActivity;
  [Singular start:config];
}
    
// openURLContexts

- (void)scene:(UIScene *)scene openURLContexts:(nonnull NSSet *)URLContexts {
  // Starts a new Singular session on cold start from deeplink scheme

  SingularConfig *config = [self getConfig];
  config.openUrl = url;
  [Singular start:config];
  
  // Add custom code here to Redirect to Non-Singular deep links

  //...
}
For Older iOS Versions (Objective-C AppDelegate)
// INITIALIZE THE SDK IN THE FOLLOWING APPDELEGATE FUNCTIONS

    
// didFinishLaunchingWithOptions

- (BOOL)application:(UIApplication *)application 
  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Starts  new session when  user opens the app if session timeout passed/opened using Singular Link

    SingularConfig *config = [self getConfig];
    config.launchOptions = launchOptions;
    [Singular start:config];
    return YES;
}
    
// continueUserActivity

- (BOOL)application:(UIApplication *)application 
  continueUserActivity:(NSUserActivity *)userActivity 
    restorationHandler:(void (^)(NSArray<id> *restorableObjects))restorationHandler {
      // Starts a new session when the user opens the app using a Singular Link while it was in the background

      SingularConfig *config = [self getConfig];
      config.userActivity = userActivity;
      [Singular start:config];
      return YES;
}
    
// openURL

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url 
  options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options{
    // Starts new session when user opens the app using a non-Singular link, like a traditional app scheme.

    SingularConfig *config = [self getConfig];
    config.openUrl = url;
    [Singular start:config];
    // Add custom code here to Redirect to non-Singular deep links

    //...
    return YES;
}
For Older iOS Versions (Swift AppDelegate)
Important:
  • AppDelegate will initialize Singular only for iOS 12.4 or earlier.
  • If you target iOS versions greater than 12.4, you should start using the SceneDelegate activities.
// INITIALIZE THE SDK IN THE FOLLOWING APPDELEGATE FUNCTIONS

    
// didFinishLaunchingWithOptions

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) - Bool {
     // Print IDFV to Console for use in Singular SDK Console 

     print(Date(), "-- Scene Delegate IDFV:", UIDevice().identifierForVendor!.uuidString as Any)
     
     //Initialize the Singular SDK here: 
     if let config = self.getConfig() { 
          config.launchOptions = launchOptions 
          Singular.start(config) 
     }
     
     return true
}
    
// continue userActivity

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([any UIUserActivityRestoring]?) - Void) - Bool {
     
     //Initialize the Singular SDK here: 
     if let config = self.getConfig() {
          config.userActivity = userActivity
          Singular.start(config) 
     }
     
     return true
}
     
// open url

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) - Bool {
     
     //Initialize the Singular SDK here: 
     if let config = self.getConfig() {
          config.openUrl = url
          Singular.start(config) 
     }
     
     return true
}

Notes:

  • When creating the config object, be careful to pass the correct option - userActivity or openUrl. See the sample code below and refer to the sample apps if needed.
  • Remember to comply with the various privacy laws enacted in regions where you conduct business, including GDPR, CCPA, and COPPA. For more information, see SDK Opt-In and Opt-Out Practices and review the Singular SDK functions that help you comply with data privacy laws.
  • To initialize the SDK, you need your Singular SDK Key and SDK Secret. You can get them in the Singular platform at "Developer Tools > SDK Integration > SDK Keys".

Frequently Asked Questions and Issues

Refer to this section if you encounter any issues or errors when building your test app.

Why am I receiving a "Sandbox: rsync.samba(15813) deny(1) file-read-data..." error?

In Xcode 15, there’s a new option called "User Script Sandboxing" that plays a crucial role in the build The purpose is to prevent scripts from making unintended changes to the system, thereby enhancing the stability and security of the build. When enabled, the build system restricts user scripts to disallow undeclared input/output dependencies. This is problematic for the Singular SDK, as it needs to run scripts to dynamiclly link dependencies.

To resolve the issue:

  1. Navigate to Build Settings > Build Options.
  2. Adjust "User Script Sandboxing" to a value "No"
Why am I receiving a "No module named Singular" error?
  1. Check that the Bridging Header was created.
  2. Validate the Bridging Header file is linked in Build Settings > Objective-C Bridging Header.
Why am I receiving an "Arm64" build error?

In some cases, the iOS Simulator requires arm64 to be excluded in Build Settings > Excluded Architectures.

excluded_architectures_arm64.png

I implemented Global Properties. Why am I not seeing them in the Testing Console?

Global Properties are currently not displayed in the Testing Console. They will be added in the future. Use Export Logs to validate this functionality.

Why am I getting a logging error?

The following common logging errors can be ignored:

  • [logging] duplicate column name: singular_link in "ALTER TABLE sessions ADD COLUMN singular_link TEXT DEFAULT NULL"
  • [logging] duplicate column name: payload in "ALTER TABLE sessions ADD COLUMN payload TEXT DEFAULT NULL"
  • [logging] duplicate column name: sequence in "ALTER TABLE events ADD COLUMN sequence INTEGER DEFAULT -1"
  • [logging] duplicate column name: payload in "ALTER TABLE events ADD COLUMN payload TEXT DEFAULT NULL"