Compliant with GDPR, CCPA, COPPA, LGPD, PECR, PDPA, PIPEDA, and more.
UniConsent CMP 是一个用于在 iOS 中处理 GDPR IAB TCF 2.4 同意管理的软件包。您可以在"UniConsentDemo"目录中找到集成了 UniConsent CMP 的演示应用。
将 UniConsent.xcframework 添加到您的项目中。在目标的 General > Frameworks, Libraries, and Embedded Content 中,将 UniConsent.xcframework 设置为 Embed & Sign。
在集成 SDK 之前,请在 UniConsent 控制面板 中自定义同意横幅的外观,使其与您的品牌一致并优化同意率。
前往 Projects → 选择您的项目 → Settings → Step 5: UI & Style Settings 进行配置:
如需更精确的控制,请在 Step 5 的 CSS Content 字段中添加自定义 CSS。建议通过此方式使横幅在您的应用中看起来更加原生,以获得最佳同意率:
/* 示例:设置接受按钮以匹配您的品牌 */
.unic-btn-accept {
background-color: #4CAF50;
border-radius: 8px;
font-weight: 600;
}
/* 示例:调整横幅字体 */
.unic-banner {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* 示例:降低拒绝按钮的突出程度 */
.unic-btn-reject {
background-color: transparent;
border: 1px solid #ccc;
color: #666;
}
提示: 精心设计品牌风格的同意界面通常能获得更高的同意率。用户更愿意与符合他们期望的外观和风格的横幅积极互动。
要在应用中使用 UniConsent CMP,请按照以下步骤操作:
使用从您的客户经理处获取的 App ID 初始化 CMP:
import UniConsent
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Init CMP with appId
// initialize() loads the location, config and vendor list over the network: keep it off the main thread
DispatchQueue.global(qos: .userInitiated).async {
UniConsentCMP.shared.initialize(apiId: "YOUR_APP_ID_CHANGE_THIS")
}
return true
}
显示 CMP 界面:
// Display CMP as full-screen modal (default)
UniConsentCMP.shared.setUIStage(.GDPRFirstScreen)
UniConsentCMP.shared.launchCMP(rootVC: self)
// Display CMP as a modal bottom sheet
UniConsentCMP.shared.launchCMP(rootVC: self, displayMode: .modalSheet)
// Display CMP as a modal bottom sheet at 60% of the available height
UniConsentCMP.shared.launchCMP(rootVC: self, displayMode: .modalSheet, heightRatio: 0.6)
// Display CMP as a center dialog
UniConsentCMP.shared.launchCMP(rootVC: self, displayMode: .dialog)
// Display CMP as a center dialog at 80% width and 60% height
UniConsentCMP.shared.launchCMP(rootVC: self, displayMode: .dialog, widthRatio: 0.8, heightRatio: 0.6)
可用的显示模式(CMPDisplayMode):
.fullScreen — 全屏模态展示(默认).modalSheet — 底部弹出面板,带拖动手柄(iOS 15+).dialog — 居中对话框,带暗色背景自 SDK 26.8.0 版本起可用。
两个比例参数均为可选,所有显示模式不传它们也能正常工作。仅在需要覆盖默认尺寸时才传入比例(相对屏幕的比例,会被限制在 0.1...1.0 范围内):
| 参数 | 适用于 | 省略时的默认值 |
|---|---|---|
heightRatio | .modalSheet、.dialog | 面板:可用高度的 90%。对话框:屏幕的 70% |
widthRatio | 仅 .dialog | 屏幕宽度的 90%,最大 500pt |
不适用于所选显示模式的比例会被忽略(.fullScreen 会忽略两者)。在 iOS 16+ 上,面板会精确使用 heightRatio;iOS 15 会回退到系统的 medium detent(比例 ≤ 0.5)或 large detent(比例 > 0.5)。
使用 CMPEventHandler 监听 SDK 生命周期事件。这是了解 SDK 初始化完成时间、界面显示时间和关闭时间的推荐方式。
可用事件(CMPEventType):
.sdkInit — SDK 初始化开始.ready — SDK 初始化完成(GEO、配置和供应商已加载).uiDisplay — 同意界面已显示.uiClose — 同意界面已关闭在调用 initialize() 之前订阅事件以接收所有事件:
import UniConsent
@main
class AppDelegate: UIResponder, UIApplicationDelegate, CMPEventHandler {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UniConsentCMP.shared.subscribe(self)
DispatchQueue.global(qos: .userInitiated).async {
UniConsentCMP.shared.initialize(apiId: "YOUR_APP_ID")
}
return true
}
func handle(event: CMPEvent) {
switch event.type {
case .ready:
// SDK is ready — check consent and launch CMP if needed
// events arrive on the thread that called initialize(): show UI on the main thread
DispatchQueue.main.async {
if let rootVC = UIApplication.shared.connectedScenes
.compactMap({ ($0 as? UIWindowScene)?.keyWindow?.rootViewController }).first {
UniConsentCMP.shared.launchCMPIfNeeded(rootVC: rootVC)
}
}
case .uiClose:
// User finished interacting with consent UI
print("TC String:", UniConsentCMP.shared.getTCString())
default:
break
}
}
}
您也可以从视图控制器中订阅:
class ViewController: UIViewController, CMPEventHandler {
override func viewDidLoad() {
super.viewDidLoad()
UniConsentCMP.shared.subscribe(self)
}
func handle(event: CMPEvent) {
switch event.type {
case .uiDisplay:
print("CMP UI is now visible")
case .uiClose:
print("Consent given, vendor 1 allowed:", UniConsentCMP.shared.isAllowVendorById(vendorId: 1))
default:
break
}
}
}
取消订阅:
UniConsentCMP.shared.unsubscribe(self)
您也可以使用 CMPUIDelegate 监听同意关闭事件:
class ViewController: UIViewController, CMPUIDelegate {
func showCMP() {
UniConsentCMP.shared.view.delegate = self
UniConsentCMP.shared.setUIStage(.GDPRFirstScreen)
UniConsentCMP.shared.launchCMP(rootVC: self, displayMode: .modalSheet)
}
func onDismiss() {
// Called when the user closes the consent UI
print("TC String:", UniConsentCMP.shared.getTCString())
}
}
当供应商列表更新时自动检查同意是否过期:
// Automatic check if consent is expired when vendorList updates
if UniConsentCMP.shared.shouldRequestConsent() {
UniConsentCMP.shared.launchCMP(rootVC: self)
}
如需获取 tcString:
// Get tcString if required
UniConsentCMP.shared.getTCString()
读取同意状态:
// Check consent for a specific IAB purpose
UniConsentCMP.shared.isAllowPurposeById(purposeId: 1)
// Check consent for a specific IAB vendor
UniConsentCMP.shared.isAllowVendorById(vendorId: 1)
// Get all allowed purpose/vendor IDs
UniConsentCMP.shared.getAllowedPurposeIds()
UniConsentCMP.shared.getAllowedVendorIds()
// Check if GDPR applies
UniConsentCMP.shared.gdprApplies
// Check if any consent has been given
UniConsentCMP.shared.isConsentGiven()
如需重置同意状态:
// Reset consent status if required
UniConsentCMP.shared.clearConsentData()
自 SDK 26.7.0 版本起可用。如果您构建自己的首层同意界面而不显示 CMP webview,可以直接保存同意状态:
// Grant consent for all purposes, special features and vendors
let saved = UniConsentCMP.shared.agreeAll()
// Reject all purposes, special features and vendors
let saved = UniConsentCMP.shared.rejectAll()
这两个 API 保存的同意值与 CMP UI 流程相同(IAB TCF 键、附加同意和 consent mode 信号),并向订阅者触发相同的 .uiClose 事件。
对于美国和加拿大用户,这两个 API 还会按照与 CMP 界面相同的规则,保存适用于该用户的 CCPA、US State Privacy 和 IAB TCF 加拿大选择,并写入 IAB GPP 和 US Privacy 键。请在 UniConsent 控制台的项目设置中开启 API-Only 模式,这些地区便不会显示 CMP 横幅。
在适用 Cookie 类别横幅的地区,它们还会保存 Cookie 类别选择(全部类别,或仅“绝对必要”),以及项目的自定义目的和自定义供应商。
要确定显示哪个首层界面,请调用 getPolicy()(自 SDK 26.10.0 版本起)。它按照与 CMP 相同的规则返回适用于该用户的政策:gdpr(IAB TCF)、tcfca(IAB TCF 加拿大)、uss(US State Privacy)、ccpa、categories(见下文 Cookie 类别)或 none。
let cmp = UniConsentCMP.shared
if cmp.shouldRequestConsent() {
// 用户需要做出选择:显示该政策对应的首层界面
let policy = cmp.getPolicy() // gdpr、tcfca 或 categories
}
注意: 这些 API 会同步通过网络获取供应商列表和项目配置,如果无法保存同意状态(例如离线),则返回
false。请在initialize()之后调用,并避免在主线程调用。重要: 您的自定义首层界面仍须满足 IAB TCF 要求(披露目的和供应商,并以同等显著程度提供同意和拒绝选项)。用户应仍能打开完整的 CMP 界面(
launchCMP())进行精细化选择。
如果您的应用打开了一个加载带有 UniConsent CMP 标签的网页的 WKWebView,您可以传递原生同意状态,这样 CMP 标签会识别现有的同意,不会再次显示横幅。
在加载网页之前调用 syncConsent(to:):
import WebKit
import UniConsent
let webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration())
// Inject native consent before page load
UniConsentCMP.shared.syncConsent(to: webView)
// Then load your web page
webView.load(URLRequest(url: URL(string: "https://example.com")!))
// Initialize with event handler
@interface AppDelegate () <CMPEventHandler>
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[UniConsentCMP shared] subscribe:self];
// initialize loads the location, config and vendor list over the network: keep it off the main thread
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
[[UniConsentCMP shared] initializeWithApiId:@"YOUR_APP_ID"];
});
return YES;
}
- (void)handleWithEvent:(CMPEvent *)event {
switch (event.type) {
case CMPEventTypeReady:
NSLog(@"CMP ready");
break;
case CMPEventTypeUiClose:
NSLog(@"TC String: %@", [[UniConsentCMP shared] getTCString]);
break;
default:
break;
}
}
@end
// Display CMP (full screen by default)
[UniConsentCMP.shared launchCMPWithRootVC:self];
// Display CMP with a specific display mode
// Use CMPDisplayModeFullScreen, CMPDisplayModeModalSheet, or CMPDisplayModeDialog
[UniConsentCMP.shared launchCMPWithRootVC:self displayMode:CMPDisplayModeModalSheet];
可以在指定界面打开 CMP,例如从应用中的“隐私设置”链接打开。请求的界面总会打开。对于美国用户,设置界面会打开适用于他们的 CCPA 或 US State Privacy 设置。加拿大用户看到的是 IAB TCF 界面的 IAB TCF Canada 版本。
| 界面 | 显示内容 |
|---|---|
.GDPRFirstScreen | 首层界面(横幅) |
.GDPRSecondScreen | 设置(目的) |
.GDPRVendorsScreen | 供应商 |
.CCPAFirstScreen | CCPA 首层界面 |
.CCPASecondScreen | CCPA 设置 |
.USStateFirstScreen | US State Privacy 首层界面 |
.USStateSecondScreen | US State Privacy 设置 |
.CategoriesFirstScreen | Cookie 类别首层界面(横幅) |
.CategoriesSecondScreen | Cookie 类别设置 |
let cmp = UniConsentCMP.shared
// 打开设置界面
cmp.setUIStage(.GDPRSecondScreen)
cmp.launchCMP(rootVC: self)
// 仅在需要同意时打开 CMP(欧盟和 IAB TCF 加拿大),返回是否已打开
let opened = cmp.launchCMPIfNeeded(rootVC: self)
// SDK 和 CMP 页面的调试日志,正式版本请使用 .error(默认)
cmp.setLogLevel(.debug)
// 仅用于测试:按其他地区的用户显示 CMP,"EU"、"CA"(加拿大)、"CAQC"(魁北克)、"US" 加上州代码(如 "USCA" 或 "USTX")、"BR"、"CN"、"ZA"、"VN" 或其他任何国家代码,或 "N"(以上地区以外)。也适用于 getPolicy() 和 shouldRequestConsent();正式版本中请删除
cmp.setTestRegion("CA")
// 如果 CMP 已打开则关闭
cmp.dismissCMP()
// 用户已做出选择:IAB TCF、CCPA、US State Privacy 或 IAB TCF 加拿大
let given = cmp.isConsentGiven()
// IAB TCF 同意创建后的天数,没有同意时为 0
let days = cmp.getConsentAge()
// 用户所在地区,以及美国和加拿大用户的州或省代码(未知时为空)
let geo = cmp.getGEO()
let state = cmp.getGEOState()
CMP 会按照 IAB 规范,将 IAB GPP 键和 US Privacy 键存储到应用的 UserDefaults中。应用内的广告 SDK 会从那里读取,无需修改代码。在 UniConsent 控制台的项目设置中启用 GPP、US State Privacy 或 IAB TCF 加拿大后会写入 GPP 键;只要启用 CCPA,就会写入 US Privacy 键。
| 键 | 值 |
|---|---|
IABGPP_HDR_Version | GPP 版本(1) |
IABGPP_HDR_Sections | GPP 字符串中的部分 ID,以 _ 分隔 |
IABGPP_HDR_GppString | 完整的 GPP 字符串 |
IABGPP_GppSID | 适用于该用户的部分 ID,以 _ 分隔;均不适用时为 -1 |
IABGPP_2_String | IAB TCF EU 字符串(适用 GDPR 时) |
IABGPP_5_String | IAB TCF 加拿大字符串(适用加拿大 TCF 时) |
IABGPP_6_String | US Privacy 字符串(适用 CCPA 时) |
IABGPP_7_String | US National(usnat)字符串(适用 US State Privacy 或 CCPA 时) |
IABGPP_8_String 至 IABGPP_27_String | 用户所在州的部分(8 加利福尼亚至 27 罗得岛),适用 US State Privacy 时写入,或在 CCPA 下写入加利福尼亚,遵循 GPP Sections 设置 |
IABUSPrivacy_String | 只要启用 CCPA 就会写入:在 CCPA 地区为用户的选择,否则为 1--- |
每次加载 CMP 以及每次做出同意选择后,这些键都会更新。关闭 GPP 后,IABGPP_ 键会被删除。如需自行读取:
let gppString = UserDefaults.standard.string(forKey: "IABGPP_HDR_GppString") ?? ""
let gppSid = UserDefaults.standard.string(forKey: "IABGPP_GppSID") ?? ""
应用支持与网站相同的美国和加拿大选项:CCPA、US State Privacy(包括州列表、GPP Sections 和 MSPA 设置)以及 IAB TCF 加拿大。对于适用 IAB TCF 加拿大的加拿大用户,在其做出选择之前,shouldRequestConsent() 会返回 true。美国隐私采用退出(opt-out)模式,因此不会主动询问美国用户:当您的应用打开 CMP 时(例如通过隐私设置链接),他们会看到美国横幅或设置。在此之前,启用 CCPA 或 US State Privacy 时,SDK 会为美国用户写入默认的 US Privacy 和 GPP 键,不显示任何内容:未退出;若设备关闭了广告跟踪,则为已退出。新的 Mobile App 项目默认关闭 US State Privacy,请在控制台中开启。
当用户在设备上关闭广告跟踪时,CMP 会将其视为美国用户对出售、共享和定向广告的退出,与网站上的 Global Privacy Control 相同。在 iOS 上指 App Tracking Transparency 中不允许跟踪;在 Android 上指已删除广告 ID 或限制广告跟踪(应用包含 Google Play 服务时通过其读取)。用户之后在横幅中保存的选择优先。
SDK 也会返回已存储的字符串:
let gppString = UniConsentCMP.shared.getGPPString()
let gppSid = UniConsentCMP.shared.getGPPSID()
let usPrivacy = UniConsentCMP.shared.getUSPrivacyString()
自 SDK 26.10.0 版本起可用。当某个地区使用 Cookie 类别而不是 IAB TCF 或美国政策时,CMP 会显示包含四个类别的横幅:绝对必要、性能、功能性、定向或广告。适用于:
选择加入(opt-in)类别初始只开启“绝对必要”,在用户做出选择之前,以及您更改配置版本之后,shouldRequestConsent() 会返回 true。选择退出(opt-out)类别初始开启所有类别(在美国,若设备关闭了广告跟踪,则关闭“定向或广告”),应用不会主动询问:用户可通过您的隐私设置链接更改。选择退出适用于其他国家的 opt-out 规则,以及应用中位于美国的 Consent Categories 项目。
使用 .CategoriesFirstScreen(横幅)和 .CategoriesSecondScreen(设置)打开类别界面。setTestRegion() 也支持 BR、CN、ZA、VN 及其他国家代码,用于测试这些地区。
自 SDK 26.10.0 版本起可用。您在 UniConsent 控制台项目设置中添加的自定义目的和自定义供应商会显示在 CMP 界面中:自定义目的显示在 IAB TCF 首层界面和设置以及 Cookie 类别设置中,自定义供应商显示在 IAB TCF 供应商列表中。在 CCPA 和 US State Privacy 设置中,只列出敏感的自定义目的。在 Cookie 类别设置中,自定义供应商跟随“定向或广告”类别。在不适用 IAB TCF 的地区(Cookie 类别和美国),自定义供应商以只读方式列在菜单的供应商列表中,并在 Cookie 类别设置的“定向或广告”下列出。
要据此启动或停止您自己的 SDK,请询问 SDK 应用现在是否可以使用某个自定义目的或自定义供应商。结果遵循用户适用的政策,规则与 CMP 用于标签的规则相同:
let cmp = UniConsentCMP.shared
if cmp.isCustomPurposeConsented(1) {
// 启动自定义目的 1 涵盖的 SDK
}
let vendorAllowed = cmp.isCustomVendorConsented(2)
let all = cmp.getCustomConsents() // ["purposes": ["1": true], "vendors": ["2": false]]
在 CMP 关闭或同意变化后请再次调用。项目中不存在的 ID,以及 SDK 就绪前的所有 ID,都返回 false。
CMP 可以列出您应用中的第三方 SDK(广告网络、分析、归因等),包括其公司、用途和隐私政策。用户在供应商列表中查看它们,在所有地区均为只读:没有开关,您的应用仍根据用户对目的或 Cookie 类别的选择来开启或关闭各个 SDK。
要设置列表,请在应用项目的 Step 4: Advanced Features 中开启 Enable Third-party SDKs,在 Third-party SDKs 标签页中选择您的 SDK 或添加自己的 SDK,然后保存并发布。您的应用中无需任何代码。
设置默认同意状态键值:
<key>FIREBASE_ANALYTICS_COLLECTION_ENABLED</key> <false/>
<key>GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE</key> <false/>
<key>GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE</key> <false/>
<key>GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA</key> <false/>
<key>GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS</key> <false/>
根据同意标志控制分析功能:
// SDK Version <= 25.6.1
public func onDismiss() {
if(UniConsentCMP.shared.isAllowPurposeById(purposeId: 1)) {
Analytics.setConsent([
.analyticsStorage: .granted,
.adStorage: .granted,
.adUserData: .granted,
.adPersonalization: .granted,
])
Analytics.setAnalyticsCollectionEnabled(true);
} else {
Analytics.setConsent([
.analyticsStorage: .denied,
.adStorage: .denied,
.adUserData: .denied,
.adPersonalization: .denied,
])
Analytics.setAnalyticsCollectionEnabled(false);
}
// other logics such as send analytics events
}
更多信息请参阅 Set up consent mode for apps
getGPPString()、getGPPSID() 和 getUSPrivacyString().USStateSecondScreen 打开 US State Privacy 设置;新增 launchCMPIfNeeded()、setLogLevel() 和 getGEOState().CCPAFirstScreen 和 .USStateFirstScreen 打开 CCPA 和 US State Privacy 首层界面;新增 setTestRegion(),可按其他地区(例如加拿大)的用户测试 CMP.CategoriesFirstScreen 和 .CategoriesSecondScreengetPolicy();agreeAll() 和 rejectAll() 还会保存 Cookie 类别、自定义目的和自定义供应商setTestRegion() 也支持 BR、CN、ZA、VN 及其他国家代码,并适用于 getPolicy() 和 shouldRequestConsent()isCustomPurposeConsented()、isCustomVendorConsented() 和 getCustomConsents():应用按照用户适用的政策现在可以使用的自定义目的和自定义供应商隐私法规、同意管理与合规动态,每月几封邮件。