iOS — Setup
This page takes you from an empty Xcode project to an integration ready for the App Store, in order. Steps 1 to 6 are the integration itself; steps 7 to 10 cover US users, testing, the release and upgrades.
Every Swift snippet below compiles as written against the published 2.0.0 package, except those marked fragment, which go inside your own code.
1. Requirements
iOS deployment target
15.0 or later, whatever the channel
Xcode
Verified with Xcode 27.0; earlier versions were not tested. The package declares Swift tools 5.9, which needs Xcode 15 or later
Swift
5.9 or later
Sirdata
A partner ID and a config ID, from the Sirdata Console
The 2.0.0 binary itself is built with Xcode 26, the version pinned in the SDK's build recipe. It is a static framework with an Objective-C interface; the consent UI ships as Swift sources that your Xcode compiles.
The partner ID and the config ID select the publisher configuration the SDK downloads at launch: texts, theme, purposes, vendors, scope and capping. Changing the configuration in the Sirdata Console takes effect without an app release.
Licence. The SDK is proprietary software, licensed and not sold, under the Sirdata CMP Mobile SDK Proprietary Licence v1.1, which is also the LICENSE file of the Swift Package Manager and CocoaPods packages. Shipping the SDK in an app made available to users requires a valid, current subscription to the Sirdata Consent Management Platform; one taken through the Sirdata Console is enough, and no signed agreement is required (section 25.1). Without a subscription, the licence is a 90-day evaluation grant: internal evaluation, development, testing and integration, with no distribution to third parties or to an app store (section 1.4). Subscriptions: sirdata.com; licence questions: legal@sirdata.com.
2. Add the SDK to your project
Use Swift Package Manager or CocoaPods: both deliver the core framework and the SwiftUI consent UI. The manual XCFramework has no UI.
Swift Package Manager in Xcode (recommended)
File → Add Package Dependencies…, and enter
https://github.com/SirDataFR/sirdata-cmp-ios-spm.Set the dependency rule to Up to Next Major Version, from 2.0.0.
Add the SirDataCMP product to your app target.
Swift Package Manager in a Package.swift
Declare the package, then the product in each target that uses it. The package: label is the last component of the URL:
CocoaPods
In your Podfile:
Run pod install, then open the .xcworkspace, not the .xcodeproj. If CocoaPods cannot find version 2.0, run pod install --repo-update. The pod works with or without use_frameworks!.
The pod is named SirdataCMP, with a lowercase d. Keep the ~> 2.0 constraint: the 1.x versions of that pod are a different, discontinued Sirdata SDK from 2020, built from another code base.
Manual (XCFramework)
Download SirDataCMP-2.0.0.xcframework.zip from the releases page, unzip it, drag SirDataCMP.xcframework into your project, and in your target's General → Frameworks, Libraries, and Embedded Content set it to Do Not Embed: it is a static framework, linked into your app binary.
That archive holds the core SirDataCMP module only, and none of the licence files of step 9. The SwiftUI consent UI (SirDataCMPUI, ConsentView, used in step 4) ships as Swift sources through Swift Package Manager and CocoaPods; use one of them if you want the built-in UI.
Two modules
Whatever the channel, you import two modules:
They are separate because the binary framework owns the SirDataCMP module name.
If your app links Firebase Analytics through Swift Package Manager, read Sharing consent with vendor SDKs before going further: the consent adapter needs Firebase's classes linked.
3. Initialize the SDK
Initialize as early as possible, before any ad or analytics SDK: a vendor SDK that reads storage before the CMP has identified itself behaves as though no CMP were present.
Three rules:
Exactly once. In 2.0.0, a second
initializewhile the SDK is running terminates the app (the next release ignores it and logs it). To switch to another partner or config, callshutdown()first;IosCMPApi.shared.isInitialized()tells you whether it is running.On the main thread.
App.init()andapplication(_:didFinishLaunchingWithOptions:)both run there. A call from another thread is logged, and terminates the app underdebugMode(below).Before any other SDK call. In 2.0.0, these terminate the app when the SDK is not initialized: presenting
ConsentView,getConsentState(),getConsentData(),getConsentUpdates(),getGoogleConsentState(),getLastForwardedConsentState(),getRegisteredAdapters(),getLastTcStringError(),discardPendingChanges(),acceptAll(),rejectAll(),saveConsent()andsubmitConsent(userAction:).
In 2.0.0, on iOS, a misuse terminates the app rather than throwing: the SDK is written in Kotlin, and its exceptions do not cross into Swift. The next release (unreleased) removes this: before initialize, the getters return a neutral value (nothing selected, nothing granted, nil), acceptAll(), rejectAll(), saveConsent() and submitConsent(userAction:) throw a Swift error you can catch, a second initialize is ignored and logged, and ConsentView shows a message with a Close button. The three rules remain the correct integration.
The snippets use partner 1020 and config "hmDnl", an example configuration. Replace them with your app's own IDs from the Sirdata Console.
SwiftUI app
UIKit app
partnerId is an Int32 in Swift: write Int32(id) if you hold it as an Int.
IABTCF_CmpSdkID is written to UserDefaults.standard synchronously during this call, before any network work, as the IAB specification requires. Everything else — publisher configuration, vendor lists, stored consent — loads asynchronously afterwards.
Configuration options
gdprDefaultApplies(defaulttrue) is used only before the configuration has loaded, and only when the device's locale carries no region. Once the configuration is loaded, the Sirdata API decides whether GDPR applies.debugMode(defaultfalse) makes a call toinitialize,shutdownorsetCcpaOptOut(optedOut:)made off the main thread terminate the app instead of only being logged, so the fault shows in your own debug builds. It has no other effect. Keep it under#if DEBUG.apiRoot,cacheApiRootandconsentApiRootdefault to the Sirdata hosts (api.,choices.andmychoice.consentframework.com). Leave them as they are.language(language:)is accepted but has no effect in 2.0.0. The UI language is the one set in your configuration; when it sets none, the device's preferred language if the Sirdata API serves it, then the configuration's default language, then English.
There is no theme option on iOS: colours, logo and banner position come from your configuration. When the device is in dark mode, the configuration's dark-mode colours are used if it defines them.
4. Present the consent UI
Let the SDK decide whether to present it. Do not test the TC string yourself: a partially answered consent must re-open the banner, an active capping window must not, and neither is visible from the string.
SwiftUI
awaitConsentDisplayDecision() waits for the publisher configuration, then applies the same cascade as the web CMP: GDPR applicability, stored consent, cookie wall, the three capping windows, and whether the stored choice still covers every active purpose, partner and publisher purpose. It also returns how to present: a choice missing only at partner level opens the banner in "apply choices" mode, which asks only about what is left to decide. With the cookie wall enabled in your configuration, the banner comes back at every launch until the user grants purpose 1.
On a dead network the wait lasts until initialization gives up, which can take the sum of the HTTP timeouts. Behind a launch screen, bound it — past the deadline you get the decision as it stands, provisional if the configuration has not arrived (fragment):
ConsentView renders the whole flow (banner → purposes → partners), themed from your configuration. Its initializer is ConsentView(onFinish:initialScreen:workflow:): onFinish is called when the user is done, and initialScreen: .purposes opens straight on the preferences screen. The view fires cmpuiclosed itself when it closes. Call notifyBannerShown() each time you present it.
UIKit
Present ConsentView in a UIHostingController, full screen over a clear background: the view draws its own dimmed backdrop and positions the banner itself.
Before shipping this full-screen presentation, read the 2.0.0 known issue at the end of the next section: it has no way out when the configuration could not be loaded.
Closing the sheet without answering
In a .sheet, the user can swipe the banner down. That closes it without saving anything: the SDK reports it to Sirdata as a close without a choice, fires cmpuiclosed, and the banner shows again at the next launch. To prevent it — with a cookie wall, for instance — disable the gesture on the view (fragment):
The full-screen UIKit presentation above has no such gesture.
Known issue in 2.0.0: no way out while the configuration is missing. If the configuration could not be loaded at this launch — the device is offline, for instance — the decision can still ask for the banner (reason configNotLoaded), but ConsentView then shows only a loading indicator, with no close control. In a plain .sheet the user can still swipe it down. With .interactiveDismissDisabled(), or in the full-screen UIKit presentation above, nothing closes it. If you use either, present the view only once the configuration has loaded. The next release (unreleased) fixes this: ConsentView shows a message and a Close button when the configuration could not be loaded, and a Close button under the loading indicator after five seconds. With UIKit, make this the first line of presentConsent(workflow:), so that it also covers the re-opening of step 6 (fragment):
5. Read consent in your own code
Values
These read the stored values, the ones vendor SDKs read too: the IAB keys in UserDefaults.standard (IABTCF_…, IABGPP_…, and IABUSPrivacy_String once a US choice exists). Sharing consent with vendor SDKs lists them. isGdprApplies() is the Sirdata API's answer once the configuration has loaded, and an estimate from the device's region before.
Some keys exist before any choice. initialize writes IABTCF_CmpSdkID, IABTCF_CmpSdkVersion and IABTCF_PolicyVersion. Once the configuration has loaded, the SDK writes IABTCF_gdprApplies, IABTCF_PolicyVersion again (from the loaded vendor list), IABTCF_PublisherCC, IABTCF_PurposeOneTreatment, IABTCF_UseNonStandardTexts, the publisher bit strings (IABTCF_PublisherConsent, IABTCF_PublisherLegitimateInterests and the two IABTCF_PublisherCustomPurposes… keys) and, for a US user, the GPP keys with the US section. IABTCF_TCString and the purpose and vendor consents (IABTCF_PurposeConsents, IABTCF_VendorConsents…) appear only once a choice is recorded.
React to changes
A Kotlin flow cannot be collected from Swift: observe the SDK's update counter with an UpdateCounterWatcher. Create it after initialize, keep a reference to it, and close it when you are done.
The callback runs on the main thread. It fires on every change of the selection, including toggles in the open consent UI that are not saved yet, so re-read the stored values each time rather than counting calls: they change only when the user saves. UserDefaults.didChangeNotification works too, but fires on every write to the app's defaults.
A US choice — saved on the US screen, or recorded with setCcpaOptOut(optedOut:) — does not change the counter: re-read getUspString() and getGppString() when the consent UI closes (in onFinish), and right after your own calls to setCcpaOptOut.
The in-app TCF API (tcfApi()) has an addEventListener command, but in 2.0.0 its callback does not say which event fired: read the stored values from it too.
Google Consent Mode
IosCMPApi.shared.getGoogleConsentState() returns the Google signals the SDK derives from the stored TCF choice (adStorageGranted, analyticsStorageGranted, adUserDataGranted, adPersonalizationGranted). The SDK forwards consent to Firebase Analytics itself: see Sharing consent with vendor SDKs.
Your own consent screen
Instead of ConsentView, a screen of your own can record a choice with try await IosCMPApi.shared.acceptAll() or rejectAll(). Each returns a ConsentResult: ConsentResult.Success, or ConsentResult.Error with a message. Call notifyBannerShown() when your screen appears and notifyBannerClosed() when it closes, so that the in-app TCF API fires cmpuishown and cmpuiclosed. recordAskLater() records an "ask me later": the banner stays hidden for one hour. Every method is in the Reference.
6. Re-open from a settings screen
Users must be able to change their choices at any time. Give them an entry point — a "Privacy choices" row in your settings — that presents the view in the manual workflow:
With UIKit, call presentConsent(workflow: .manualDisplay) from step 4.
In the manual workflow the banner has a close cross at the top and a "Reject all" button, whatever refusal type and style your configuration sets, and the purposes and partners screens show the saved choices. When it appears, the view restarts the capping window of your configuration and reports a re-visit (load:revisit): do not call recordPrompt() yourself.
7. US users
When GDPR does not apply, the SDK never asks for the banner to be shown: awaitConsentDisplayDecision() returns shouldDisplay == false, with reason gdprNotApplies. A US user therefore sees nothing unless your app opens the consent UI.
isCcpaApplies() tells you whether the US regime applies: the Sirdata API's answer once the configuration has loaded, the device's region before. When it is true, ConsentView shows the SDK's US screen instead of the TCF banner, whatever the workflow. Saving it records the user's opt-out choice: the SDK writes IABUSPrivacy_String and re-encodes the US sections of the GPP string. Until then, IABUSPrivacy_String is absent; the GPP keys, with the US section, are written as soon as the configuration has loaded, with default values until the user chooses. A US choice does not change the getConsentUpdates() counter (step 5).
So show the entry point of step 6 to US users too. If you build your own US screen instead, call IosCMPApi.shared.setCcpaOptOut(optedOut:) on the main thread, and only with the choice the user actually made — never on their behalf.
8. Test and verify your integration
To replay a first launch, delete the app, or call IosCMPApi.shared.clearConsent() and relaunch. Call it too whenever you switch to another partner or config: a stored choice belongs to the publisher that collected it.
Whether GDPR applies is decided by the Sirdata API, from the country of the device's IP address and from your configuration — not from the device's region settings. Where GDPR does not apply, the banner does not show (reason gdprNotApplies).
In a debug build, check each point in order:
Right after
initialize,UserDefaults.standard.integer(forKey: "IABTCF_CmpSdkID")is92.At the first launch, the decision has
shouldDisplay == trueand the banner appears with your configuration's texts, colours and logo. The purposes and partners screens open. Before any choice, once the configuration has loaded,IABTCF_gdprAppliesis1andIABTCF_TCStringis absent.After Accept all,
IABTCF_TCString,IABTCF_PurposeConsents,IABTCF_VendorConsentsandIABGPP_HDR_GppStringare set, andIABUSPrivacy_Stringis absent.After a relaunch, the banner does not show again.
The re-opening of step 6 shows the close cross, the "Reject all" button and the saved choices; a new choice replaces the old one.
pingsucceeds and reportscmpStatusloaded.The Xcode console shows no line starting with
[CMP]. One appears only when something needs fixing: a call off the main thread, a consent adapter that failed to forward a choice, a stored TC string that could not be read.A Release build runs on a real device.
The keys of points 1 to 3, and point 6, can be checked from code (fragment). Read IABTCF_CmpSdkID right after initialize; run the rest once the configuration has loaded — from a debug button, for instance: until then ping answers with cmpStatus stub.
If the banner never appears, the decision says why before any log does (fragment):
An active capping window (cappingActive, cappingAskLater), a stored consent that still covers everything (consentOk) or GDPR not applying (gdprNotApplies) are normal reasons for the banner not to appear.
If the configuration does not load — IosCMPApi.shared.tcfApi().isCmpLoaded() stays false a few seconds after launch — IosCMPApi.shared.getInitError() returns the error message of the failed load; an unknown partner or config ID and a network failure both end up there. The SDK does not log it. Read it then, not as soon as awaitConsentDisplayDecision() returns: in 2.0.0, at that moment it can still be nil after a failure. The next release sets it before awaitConsentDisplayDecision() returns. See also Logging and Troubleshooting.
9. Prepare the release
Your own IDs. Replace the example partner and config IDs with your app's.
debugModein debug builds only. It terminates the app on a main-thread violation, and the SDK does not check the build type: keep it under#if DEBUG.Licence. Shipping the app requires a valid, current subscription to the Sirdata Consent Management Platform; one taken through the Sirdata Console is enough (step 1).
Third-party notices. The SDK embeds open-source components under the Apache License 2.0 and the Boost Software License 1.0. When you distribute your app, reproduce the SDK's notice file and those licence texts and make them available to your users — in your app's acknowledgements screen, for instance. This is a condition of the Apache License, and of section 26.1(b) of the SDK's
LICENSE. With Swift Package Manager they are in the package, asNOTICEandTHIRD-PARTY-LICENSES/; with CocoaPods, inPods/SirdataCMP/asNOTICE-iosandTHIRD-PARTY-LICENSES/; with the manual XCFramework, take them from the package repository on GitHub.Privacy manifest. The SDK 2.0.0 ships no Apple privacy manifest (
PrivacyInfo.xcprivacy). It reads and writesUserDefaults.standard, one of the API categories (NSPrivacyAccessedAPICategoryUserDefaults) that Apple's documentation on required reason APIs covers (checked on 2026-10-05). Take it into account for your App Store submission, and ask your Sirdata contact if you have questions.App privacy details. The privacy details you give in App Store Connect include the practices of the third-party SDKs in your app (checked on 2026-10-05), this one included. It sends requests to
api.consentframework.com,choices.consentframework.comandmychoice.consentframework.com.
10. Upgrading
From Swift Package Manager 1.2.0. "Up to Next Major Version" never crosses a major version, so a rule set from 1.2.0 stays on 1.x. Change it to start from 2.0.0: in Xcode, in your project's Package Dependencies tab; in a Package.swift, from: "2.0.0". Then raise your deployment target to iOS 15.0 if it is lower. No public API was removed or renamed in 2.0.0, so your code needs no change; Versions lists what changed in behaviour.
From the 2020 SirdataCMP pod (1.x). It is a different SDK, built from another code base. Remove it and the code that calls it, then integrate this SDK from step 2, with pod 'SirdataCMP', '~> 2.0'.
Keep the two modules at the same version. The SwiftUI layer and the binary framework are released together, and both package managers keep them aligned. If Xcode reports that a type has no member the SDK's code uses, resolve the package again, and do not mix a local copy with a released version.
Next
Sharing consent with vendor SDKs — the part that makes ads work
Reference — every method and event
Logging — what the SDK writes to the console
Troubleshooting — the errors integrators actually hit
Versions — what changed in each release
Mis à jour
Ce contenu vous a-t-il été utile ?
