Skip to main content
Skip table of contents

iOS SDK

Installation

The minimum iOS version is iOS 12.

The SDK can be integrated to iOS projects using different ways:

The latest SDK version can be found here

Swift Package Manager (SPM)

  • Go to your project → Package Dependencies tab

  • Click the + sign to add a new package

  • Add the following SPM dependency URL

    CODE
    https://github.com/Utiq-tech/UTIQ-iOS-SDK

CocoaPods

  • If it is the first time to add a CocoaPod to your project, then you should follow this official tutorial to install CocoaPods.

  • Add this pod to your project’s podfile pod 'UTIQ', or you can specify an explicit version pod 'UTIQ', 'LATEST_VERSION' where 'LATEST_VERSION' should be a number.

  • Install the pod

    CODE
    cd {PATH_TO_YOUR_PROJECT}
    pod install

If for some reason you got an error saying that the latest version is not found, please try to update the SDK pod using this command pod update UTIQ Please make sure that you have the latest stable version.

Binary Framework

  • Head to this Repo.

  • Download this file UTIQ.xcframework.

  • Drag and drop it to your project.

  • Make sure you always have the latest stable update of the framework.

  1. Initialize the SDK.

Please make sure that you have the latest stable version.

Initializing the SDK

Once added to your app, you can simply call the SDK initializer in AppDelegate or the main class of your iOS application.

Please email us for a new token and a config file specific to your mobile App.

Basic initializing

SWIFT
UTIQ.shared.initialize(sdkToken: SDK_TOKEN)

Initializing with custom options

UTIQOptions() is an optional initialization parameter, so you can initialize the SDK without passing it, but in case there are options that you want to enable or disable specifically, this can be done by enabling or disabling the options in theUTIQOption and passing it to the initializer.

SWIFT
let fallbackConfigJson : String = "{ /* fallbackConfigJson */ }"
let options = UTIQOptions().enableLogging().setFallBackConfigJson(json: configJsonString)
UTIQ.shared.initialize(sdkToken: "test123", options: options)

Currently, enable debugging and the fallback config are the only available options.

Check if the SDK is initialized

At some point, you will need to check if the SDK is initialized before doing anything else.

SWIFT
UTIQ.shared.isInitialized()

didInitializeWithResult Closure

You would need to do this if you want to do something with the SDK very early (for example in the splash screen) and you want to make sure that the SDK is initialized before using any function from it, however if you will use the SDK functions in a class that will require going through multiple steps, such as the cart page in an e-commerce app, most probably you won’t need to use this function because the SDK will be already initialized by the time of reaching that page or class, so it is your call to decide when to use that check and when not to.

SWIFT
UTIQ.shared.didInitializeWithResult { [weak self] in
        // Success Action
} failure: { [weak self] error in
        // Failure Actio
}

Basic usage

Once the SDK is initialized, all the functions can be called by calling UTIQ.shared. to access all the SDK functions.

Integration

Fetch Utiq data

SWIFT
try? UTIQ.shared.fetchIdConnectData(dataCallback: { [weak self] idcData in
            
        }, errorCallback: { [weak self] error in
        
})

You can use a stub token to test the Utiq service if you don’t have an eligible SIM card from one of the supported Telcos.
Please email us to generate a new stub token for your App.

SWIFT
try? UTIQ.shared.fetchIdConnectData(stubToken: STUB_TOKEN, dataCallback: { [weak self] idcData in
            
        }, errorCallback: { [weak self] error in
        
})

App Tracking Transparency (ATT)

You must use the AppTrackingTransparency framework if you want to use the AdTechPass and share it with other companies for purposes of tracking across apps and websites.

The AppTrackingTransparency framework presents an app-tracking authorization request to the user and provides the tracking authorization status.

More can be found here.

Request ATT

  • Show the ATT pop-up, if accepted proceed to the next step, if rejected then you can still proceed to the next step but you will only get the MarTechPass.

  • Show the Utiq consent pop-up, if the user accepts then proceed to the next step, else stop the flow.

ATT Validity Check - User rejects the ATT

Apple gives users an option to change or withdraw ATT from the App settings, this can be done even while the App is not open.

  1. Go to Settings > Privacy & Security > Tracking to see a list of apps requested to track your activity.

  2. Tap to turn off or on the permission track for a specific app.

In this case, if the ATT request was accepted, and then rejected later, there should be a way to check the ATT status periodically, for example on each App start and reject the Utiq consent and remove the AdTechPass if previously set.

ios-16-iphone-14-pro-settings-privacy-security-tracking-allow-apps-to-request-to-track.png

Utiq Consent

Dedicated guidelines for Utiq Privacy Requirements and Consent Experience on Mobile App can be found at this page: Consent Experience on Mobile App

The Utiq services require the user’s consent, hence it is crucial to show a prompt to the user to ask for his consent as without consent acceptance, Utiq data will not be fetched.

The SDK does not provide any function to show a consent alert/popup, so you should provide a custom popup that matches the look and feel of your App.

This consent popup should be shown to the user before calling the fetchIdConnectData() function (Only if was not shown before) to ask for the user’s consent and pass the user's choice to the SDK.

While you can use the pop-up that matches the look and feel of your App, the consent description should follow our guidelines which can be found here.

Is consent accepted

This function returns a boolean indicating the consent status of the user (accepted or rejected).

CODE
try? UTIQ.shared.isConsentAccepted()

You can use this function to check the user’s consent before showing a dialog asking for the user’s consent.

Accept consent

SWIFT
try? UTIQ.shared.acceptConsent()

Reject consent

SWIFT
try? UTIQ.shared.rejectConsent()

With this function, we can reject the user’s consent, if a user changes his mind or if we want to reset the user’s consent.

Because this is an API call, you might want to do something when the request returns either success or failure, so you can use the same function above but with success and failure closures.

SWIFT
try? UTIQ.shared.rejectConsent(successCallback: { [weak self] in
            
        }, errorCallback: { [weak self] error in
            
})

It is mandatory to prompt the user that his consent has been rejected successfully if it was accepted before and the prompt should adhere to the text in the guidelines.

consenthub URL

If there is a need for accessing the consenthub URL from within the mobile app, i.e. opening it in a web view, this can be done by calling the function that returns the consenthub URL.

SWIFT
UTIQ.shared.consentHubUrl()

In the implementation phase, you might need to pass the stub token to the consentHubUrl(stubToken: STUB_TOKENtoken) function if you are not using an eligible SIM.

User Eligibility

Before asking for the user consent and fetching IdConnect data, it is good to check if the user is on a supported Telco or not by using the following function, doing this will avoid unnecessary API calls, and consent pop-up but in all cases Utiq won’t return any data if the Telco is not supported.

SWIFT
UTIQ.shared.checkMNOEligibility {
// Success
} errorCallback: {
  print("Error \($0)")
}

The eligibility check function also accepts a stub token so you can test freely without a supported SIM.

Clear cached data

Data and cookies cached locally can be cleared easily using the following functions.

SWIFT
try? UTIQ.shared.clearData()
try? UTIQ.shared.clearCookies()

This does not delete data and cookies from our back-end, but only from the local storage of the mobile, to delete the user’s data, you should reject the function to reject the user's consent.

Error handling

All the functions with callbacks mentioned in the previous sections have success and failure callbacks/closures, the failure callback/closure returns a custom error relevant to the function, this custom error can be used to show an error or an action that can be taken based on the returned error.

All of the following errors are of type UtiqError that inherits from NSError.

Showing errors flagged from the SDK to the end user is not recommended.
Errors are returned so an action can be taken by developers based on the error code.

Error

Code

Description

HttpException

HTTP Codes

Denotes an HTTP exception with an error message and a status code.

 InvalidSdkTokenException

-1982

An invalid token was used to initialize the SDK.

SdkAlreadyInitializedException

-1983

This error will be thrown on an attempt to initialize the SDK after it has already been initialized.

NoInternetConnectionException

-1984

Denotes that the internet is not reachable through WiFi or cellular.
If WiFi or cellular is connected but no internet connection, this error won’t be thrown.

ConfigFileNotFound

-1985

Denotes that SDK config is not found.

SdkTokenCanNotBeEmptyException

-1986

This error will be thrown if you pass an empty token to the SDK’s initializer.

SdkNotInitializedException

-1987

This error will be thrown if you try to access any function from the SDK before or without initializing it.

UnKnowUserStatusException

-1962

When initiating the Utiq flow, the user status should be only one of these: NEW, OK, or NotCreated.
If the user status is something else, this error will be thrown.

UserFrozenUtiqForOneYearException

-1963

Indicates that a user has frozen Utiq from the consenthub for one year.

EmptySetCookieHeader

-1964

Denotes that the SetCookie header is missing from the response header.

InvalidStubTokenException

-1965

An invalid token stub was used to initialize Utiq SDK.

MnoIneligibleException

-1966

The SIM card operator cannot be used with Utiq.

 UtiqConsentNotSetException

-1967

The user has not given his consent or the user was not asked to accept or reject consent.

UserOptedOutFromUtiqException

-1968

The user has wiped his data from the consenthub, in this case, you have to clear the cached data and ask for the user’s consent then fetch IdConnect data again.

UnknownTelcoUseCaseException

-1969

Denotes that the SIM operator is of use-case that is unknown to UTIQ.

MnoUrlNotFoundException

-1970

Denotes that mobile MNO URL was not found.

DataValueNotFoundException

-1971

Denotes that data value was not found.

DataDomainNotFoundException

-1972

Denotes that data domain was not found.

UndefinedTelcoException

-1973

Denotes a Telco with a token use-case, but not one of the Utiq-enabled Telcos.

InvalidSamlSessionIdException

-1974

SAML use-case Telco with an invalid session ID.

InvalidSamlLocationURLException

-1975

SAML use-case Telco with an invalid location URL.

InvalidSamlAuthenticationURLException

-1976

SAML use-case Telco with an invalid authentication URL.

UtiqNotStartedException

-1977

fetchIdConnectData function not called.

IdConnectApiHostNotFoundException

-1978

Denotes that the ID connect API host was not found.

IdConnectDataNotFoundException

-1979

Denotes that ID connect data was not found.

GenericException

-1981

Denotes an error that is not one of the errors in this table.

UnknownException

-1990

Denotes an unknown error.

Example CMP agnostic integration (Didomi)

The following is a sample integration, feel free to organize the code the way that suits you, but please make sure to follow the general guidelines.

  1. Create a class that has all the functions needed from Didomi, it can be a singleton class or an Interface and implementation that you can inject using any DI framework or service locator.

    SWIFT
    import OSLog
    import Didomi
    
    class DidomiSdk {
        
        private var didomiEventListener: EventListener?
        private var isUtiqVendorEnabled = false
        private var isUtiqPurposeEnabled = false
        private let didomi = Didomi.shared
        //
        static let shared = DidomiSdk()
        
        private init() {
            self.didomi.setLogLevel(minLevel: OSLogType.default.rawValue)
        }
        
        func initialize() {
            /*
                The SDK will automatically use the remote configuration
                hosted by Didomi and cache it locally.
                The cached version is refreshed every 60 minutes.
                Config file example
                    {
                       "app": {
                       "name": "My App Name",
                       "privacyPolicyURL": "http://www.website.com/privacy",
                       "vendors": {
                           "iab": {
                              "all": true
                            }
                       },
                       "gdprAppliesGlobally": true,
                       "gdprAppliesWhenUnknown": true
                      }
                   }
            */
            let initializeParameters = DidomiInitializeParameters(
                apiKey: "5252a0d1-edad-4c60-8103-82aa49bd5432",
                localConfigurationPath: nil,
                remoteConfigurationURL: nil,
                providerID: nil,
                disableDidomiRemoteConfig: false,
                languageCode: nil,
                noticeID: "3RDba8E8"
            )
            self.didomi.initialize(initializeParameters)
            self.didInitialize {
                let currentUserStatus = self.didomi.getCurrentUserStatus()
                self.isUtiqVendorEnabled = currentUserStatus.vendors.first(where: { $0.key.lowercased().contains("utiq") })?.value.enabled ?? false
                self.isUtiqPurposeEnabled = currentUserStatus.purposes.first(where: { $0.key.lowercased().contains("utiq") })?.value.enabled ?? false
            }
            self.onError {
                os_log("Error while initializing Didomi SDK", log: .default, type: .error, $0)
            }
        }
        
        func startIfNeeded(viewController: UIViewController, forceStart: Bool) {
            self.didInitialize {
                self.didomi.setupUI(containerController: viewController)
                if (forceStart) {
                    self.didomi.forceShowNotice()
                }
            }
        }
        
        func startedBefore() -> Bool {
            !self.didomi.shouldUserStatusBeCollected()
        }
        
        func isUtiqEnabled() -> Bool {
            self.isUtiqVendorEnabled && self.isUtiqPurposeEnabled
        }
        
        func reset() {
            self.didomi.reset()
        }
        
        func resetUtiq() {
            self.didInitialize {
                let currentUserStatus = self.didomi.getCurrentUserStatus()
                /*
                 If you want to hardcode the ID, The vendor ID
                 can also be found in the Didomi's console in the Data Manager
                 section, select the VENDORS tab, then search for the vendor
                 you want to enable or disable, and the APP ID is the vendor ID.
                 */
                let utiqVendorId = currentUserStatus.vendors.keys.first { $0.lowercased().contains("utiq") }
                /*
                 If you want to hardcode the ID, The purpose ID
                 can also be found in the Didomi's console in the Data Manager
                 section, select the Purposes tab, then search for the purpose
                 you want to enable or disable and the APP ID is the purpose ID.
                 */
                let utiqPurposeId = currentUserStatus.purposes.keys.first { $0.lowercased().contains("utiq") }
                if(utiqVendorId != nil && utiqPurposeId != nil) {
                    _ = self.didomi.openCurrentUserStatusTransaction()
                        .disableVendor(utiqVendorId!)
                        .disablePurpose(utiqPurposeId!)
                        .commit()
                }
            }
        }
        
        func consentStatusDidChange(action: @escaping (_ enabled: Bool) -> ()) {
            /*
             Listen for changes on the user status linked to a specific vendor.
             We always need to listen for changes from Didomi as the user
             might open the screen from another place and reject his consent
             that he granted before, in this case we need to keep the synchronization
             between Didomi and Utiq
             */
            self.didInitialize {
                if (self.didomiEventListener == nil) {
                    self.didomiEventListener = self.createDidomiEventListener(action: action)
                }
                else {
                    self.didomi.removeEventListener(listener: self.didomiEventListener!)
                }
                self.didomi.addEventListener(listener: self.didomiEventListener!)
            }
        }
        
        func onError(errorAction: @escaping(_ errorMessage: String) -> ()) {
            let eventListener = EventListener()
            eventListener.onError = {
                errorAction($0.localizedDescription)
            }
            self.didomi.addEventListener(listener: eventListener)
        }
        
        func didInitialize(action: @escaping () -> ()) {
            self.didomi.onReady(callback: action)
        }
        
        private func createDidomiEventListener(action: @escaping (_ enabled: Bool) -> ()) -> EventListener {
            let didomiEventListener = EventListener()
            didomiEventListener.onPreferencesClickVendorAgree = { _, vendorId in
                if (vendorId?.lowercased().contains("utiq") ?? false) {
                    self.isUtiqVendorEnabled = true
                }
            }
            didomiEventListener.onPreferencesClickVendorDisagree = { _, vendorId in
                if (vendorId?.lowercased().contains("utiq") ?? false) {
                    self.isUtiqVendorEnabled = false
                }
            }
            didomiEventListener.onPreferencesClickAgreeToAllVendors = { _ in
                self.isUtiqVendorEnabled = true
            }
            didomiEventListener.onPreferencesClickDisagreeToAllVendors = { _ in
                self.isUtiqVendorEnabled = false
            }
            // This will be called when the agree selector switch one of the options of the second layer is selected
            didomiEventListener.onPreferencesClickPurposeAgree = { _, purposeId in
                if (purposeId?.lowercased().contains("utiq") ?? false) {
                    self.isUtiqPurposeEnabled = true
                }
            }
            // This will be called when the disagree selector switch one of the options of the second layer is selected
            didomiEventListener.onPreferencesClickPurposeDisagree = { _, purposeId in
                if (purposeId?.lowercased().contains("utiq") ?? false) {
                    self.isUtiqPurposeEnabled = false
                }
            }
            // This will be called when the agree all selector switch one of the options of the second layer is selected
            didomiEventListener.onPreferencesClickAgreeToAllPurposes = { _ in
                self.isUtiqPurposeEnabled = true
            }
            // This will be called when the disagree all selector switch one of the options of the second layer is selected
            didomiEventListener.onPreferencesClickDisagreeToAllPurposes = { _ in
                self.isUtiqPurposeEnabled = false
            }
            didomiEventListener.onPreferencesClickSaveChoices = { event in
                action(self.isUtiqEnabled())
            }
            // This will be called when Agree of the first layer is clicked
            didomiEventListener.onNoticeClickAgree = { _ in
                self.isUtiqVendorEnabled = true
                self.isUtiqPurposeEnabled = true
                action(self.isUtiqEnabled())
            }
            
            // This will be called when Disagree of the first layer is clicked
            didomiEventListener.onNoticeClickDisagree = { _ in
                self.isUtiqVendorEnabled = false
                self.isUtiqPurposeEnabled = false
                action(self.isUtiqEnabled())
            }
            return didomiEventListener
        }
    }
  2. Create a function to start and observe Didomi's status

    SWIFT
    private func startDidomiAndObserveConsentStatus(forceStart: Bool) {
            DidomiSdk.shared.startIfNeeded(viewController: self, forceStart: forceStart)
            DidomiSdk.shared.consentStatusDidChange { accepted in
                if (accepted) {
                    try? Utiq.shared.clearData()
                    // Only use stub if you are testing and do not have an eligible SIM card
                    self.stubToken = YOUR_STUB_TOKEN_GOES_HERE
                    try? Utiq.shared.acceptConsent()
                    self.fetchUtiqIds()
                } else {
                    self.rejectUtiqConsent()
                   // Do any further actions if needed
                }
            }
        }
  3. To synchronize Didomi's status with Utiq you can do the following

    SWIFT
    DidomiSdk.shared.didInitialize {
                if (DidomiSdk.shared.startedBefore()) {
                    if (DidomiSdk.shared.isUtiqEnabled()) {
                        // Only use stub if you are testing and do not have an eligible SIM card
                        self.stubToken = YOUR_STUB_TOKEN_GOES_HERE
                        try? Utiq.shared.acceptConsent()
                        self.fetchUtiqIds()
                    } else {
                        // Didomi Consent was rejected before, Utiq will not start!
                    }
                } else {
                    self.startDidomiAndObserveConsentStatus(forceStart: false)
                }
            }
  4. Then fetch Utiq IDs

    SWIFT
    // Call the function that accepts the stub token if the token is not nil, else call the other function the does not accept the stub token
    if let token = self.stubToken {
                Utiq.shared.fetchIdConnectData(stubToken: token, dataCallback: { [weak self] idcData in
                    /*
                       Do Whatever you want with the IDs
                       AttrPass => idcData.attrid
                       AdTechPass => idcData.atid
                       MarTechPass => idcData.mtid
                    */ 
                }, errorCallback: {
                    let errorCode = ($0 as NSError).code
                    let userOptedOutFromUtiqErrorCode = UserOptedOutFromUtiqException().code
                    let userFrozenUtiqForOneYearErrorCode = UserFrozenUtiqForOneYearException().code
                    if errorCode == userOptedOutFromUtiqErrorCode || errorCode == userFrozenUtiqForOneYearErrorCode {
                        DidomiSdk.shared.resetUtiq()
                    }
                    // Handle the error
                })
            }
            else {
                Utiq.shared.fetchIdConnectData(dataCallback: { [weak self] idcData in
                    /*
                       Do Whatever you want with the IDs
                       AttrPass => idcData.attrid
                       AdTechPass => idcData.atid
                       MarTechPass => idcData.mtid
                    */ 
                }, errorCallback: {
                    if $0 is UserOptedOutFromUtiqException || $0 is UserFrozenUtiqForOneYearException {
                        DidomiSdk.shared.resetUtiq()
                    }
                    // Handle the error
                })
            }
  5. Synchronize Didomi when Utiq is rejected manually from the App, i.e. from manage Utiq page.

    SWIFT
     private func rejectUtiqConsent(postAction: @escaping () -> () = {}) {
            Utiq.shared.rejectConsent {
                try? Utiq.shared.clearData()
                postAction()
            } errorCallback: {
                // Handle the error
            }
        }
  6. To Update Didomi’s status, you call this function

    SWIFT
    self.startDidomiAndObserveConsentStatus(forceStart: true)

Support and bug reporting

If you have any suggestions or you want to report a bug or ask a question, please create a new issue in this repository.

You can also check if there is a similar issue to yours that has been reported and solved before.

Contact Utiq Customer Success team at csm@utiq.com for any inquiries.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.