Changelog
3.6.16
September 10, 2026
Summary
Adds server-driven forced re-consent and remote per-country consent expiration, and folds every consent-invalidation reason into a single validity gate.
New Features
- Forced re-consent (
forceReconsent) — The server can stamp a config withforceReconsent, a Unix-seconds instant (set when Do Not Sell is newly enabled). Consent recorded before that instant is treated as invalid so the user is re-prompted. The value is validated before use: an absent, zero/negative, future, or mistakenly-milliseconds value is ignored, so a malformed stamp can never lock a user out. Consent recorded exactly at or after the stamp stays valid, and re-consenting clears the condition automatically. - Per-country consent expiration — The SDK now fetches Osano's remote country mappings (
{origin}/mappings.json, cached ~24h and best-effort) and applies each country'sconsentExpirationwindow, so consent older than the window is treated as invalid and re-prompts. Previously there was no production consent expiry (only a debug testing mode). GDPR applicability and variant continue to come from the config response, unchanged. - Native gated re-prompt — Adds
ConsentManager.showConsentIfNeeded(presenter:consentUiBuilder:type:success:deny:), which presents the consent dialog only when there is no valid consent (never consented, predatesforceReconsent, or expired), clearing the stale record first. It is opt-in and never fires on its own, so apps whose consent UI is driven by the web layer are unaffected. Also addsConsentManager.clearConsentIfInvalid().
Changes
- Single consent-validity gate —
ConsentManager.userConsent()now returnsfalsefor any invalidation reason — never consented, consent predatingforceReconsent, or expired — and every consumer (reported categories, dialog gating, analytics) inherits it. Why it matters: previously only a raw "ever consented" flag was checked, so invalidated consent could still be reported as valid. - Analytics init grant gated on validity — The initial Google Consent Mode grant now fires only for a currently-valid consent record; an invalidated or expired record publishes the essential-only (denied) baseline instead of a stale GRANT. Why it matters: a third party no longer receives a GRANT for consent the user must re-give. This also suppresses the init grant for expired consent, not just forced re-consent.
- Remote consent resync respects the signal — When repopulating consent from a previously stored server record, a record that predates
forceReconsentis no longer applied, so the resync cannot silently restore invalidated consent. - Extra launch request — Initialization now makes one additional cached, best-effort request for
mappings.json; a failure leaves consent expiration unset (fails toward not invalidating).
The consent dialog UI and existing public method signatures are unchanged; the new behavior is additive except where noted above.
3.6.15
August 26, 2026
Summary
Adds property-driven display of the consent UUID in the preference drawer and the "powered by Osano" banner logo, and enables the CCPA Do Not Sell opt-out by default.
New Features
- Consent UUID in the preference drawer — Adds the
showConsentUuidconfig flag. When enabled, the preference drawer shows a read-only row with the user's consent UUID and a button to copy it to the pasteboard; when disabled (the default), the section is hidden. The UUID is read from the existingConsentManager.uuidaccessor. - Banner logo — Adds the
showBannerLogoconfig flag. The consent dialog shows the "powered by Osano" logo below the buttons only when the flag is explicitly set to true; it is hidden by default (when the flag is unset or false). The preference drawer always shows the logo and now renders it as the bundled image (replacing the previous text link). Tapping the logo opens the Osano page, unchanged. Only the consent dialog is gated by this flag.
Changes
- Do Not Sell defaults to on —
enableDoNotSellnow defaults totrue(previouslyfalse). Why it matters: the CCPA/CPRA "Do Not Sell or Share My Personal Information" opt-out is now presented by default wherever CCPA applies, so a tenant whose config omits the flag is compliant by default instead of silently hiding a legally required opt-out. Tenants that do not want it can setenableDoNotSelltofalse. The opt-out still appears only when CCPA actually applies (an explicitccpaApplies, otherwise US browsing), andenableDoNotSellDefault(whether the user starts opted out) is unchanged and still defaults tofalse.
Other public APIs are unchanged.
3.6.14
August 18, 2026
Summary
Do Not Sell (DNS) opt-out is now driven by property configuration, matching the web CMP.
New Features
- DNS opt-out from config — Adds
enableDoNotSell,enableDoNotSellDefault, anddoNotSellCategories. The opt-out shows whenenableDoNotSellis on and CCPA applies, toggles the configured categories (default: Marketing), and defaults fromenableDoNotSellDefault. Existing consent behavior and public APIs are unchanged.
3.6.13
August 07, 2026
Summary
Fixes variant-specific timeout configuration for timer-based consent dialog variants.
Bug fixes
- Fix consent dialog auto-dismiss scheduling to respect
allowTimeoutandtimeoutSeconds.
Known Issues
- None at this time.
3.6.12
July 29, 2026
Summary
Adds SDK support for a new server-configured consent dialog layout, including its associated default category states. The layout is not generally available and must be enabled by Osano. Existing properties are unaffected.
New Features
- Added handling for an additional banner layout delivered via property configuration, including layout-specific default consent states and available banner actions.
- No changes to existing layouts, stored consent behavior, or public APIs.
Known Issues
- None at this time.
3.6.11
July 23, 2026
Summary
Adds a debug-only consent expiration testing mode to ConsentManager, letting you shorten how long stored consent stays valid so you can exercise the re-consent flow without waiting for the real expiration window. Mirrors the Android SDK's setConsentExpirationTestingMode().
New Features
-
Consent expiration testing mode:
setConsentExpirationTestingMode(_:)makes stored consent expire a set number of seconds after it was recorded. While active and past the window,userConsent()returnsfalseanduserConsentedCategories()returns essential-only, so your app re-prompts. Passnilto disable. It only takes effect in debug builds of the SDK — release builds ignore the value, so it cannot ship to production. Why it matters: You can validate consent-refresh flows in seconds instead of waiting out the full expiration window, and it is safe by construction because it is inert in release builds.Swift:
// Consent expires 10 seconds after it is recorded (debug builds only)
consentManager.setConsentExpirationTestingMode(10)
// Disable testing mode and restore normal behavior
consentManager.setConsentExpirationTestingMode(nil)Objective-C:
// Consent expires 10 seconds after it is recorded (debug builds only)
[self.consentManager setConsentExpirationTestingMode:@10];
// Disable testing mode and restore normal behavior
[self.consentManager setConsentExpirationTestingMode:nil];
Known Issues
- None at this time.
3.6.10
June 29, 2026
Summary
Adds a public jurisdiction getter to ConsentManager so you can read the visitor's effective jurisdiction as a single composite value, matching the web SDK's Osano.cm.jurisdiction and the Android SDK's getJurisdiction().
New Features
- Public
jurisdictiongetter:consentManager.jurisdictionreturns the visitor's jurisdiction as a lowercase composite of country and region (for example,usorus-ca). Previously this value was computed internally but was not accessible from your app. Why it matters: You can now read the jurisdiction in one call and in a consistent, lowercase format across platforms, instead of having no public access on iOS.
Known Issues
- None at this time.
3.6.9
June 4, 2026
Summary
Makes swipe-to-dismiss on the bottom sheet consent dialog behave like the close/X button, so a swipe records consent through the same path instead of closing the dialog silently.
Bug Fixes
- Bottom sheet swipe-to-dismiss bypassed the consent flow: Swiping the bottom sheet down now runs the same logic as tapping close/
X. Dismissible variants record consent via the existingdeny(reject-all) orsuccess(accept-all) callbacks, and non-dismissible variants ignore the swipe gesture entirely. A guard prevents programmatic accept/deny dismissals from firing the consent logic twice. Why it matters: Previously a swipe could close the dialog without going throughdenyAll()/manager.consent()/deny/success, leaving consent unrecorded. Swipe dismissal is now handled internally by the SDK with no additional integration work required.
Known Issues
- None at this time.
3.6.8
May 1, 2026
Summary
Changes how configurations are cached and requested, using server-specific headers to validate the most recent configuration is being used.
Bug Fixes
- New configuration changes were not being reflected until the internal configuration cache expired: Fixed an issue where a configuration file would still be used if it was within the caching duration, even if there was a more recent version available on the server. Why it matters: Users viewing the Consent UI would not see the most recent changes to the configuration until the local copy of the configuration file expired.
Known Issues
- None at this time.
3.6.7
April 23, 2026
Summary
Fixes a translation lookup bug that caused known translation keys to return their raw key (or an empty string) when no remote translation bundle was present, or the key was missing.
Bug Fixes
- Local bundle and fallback translations were skipped when no remote translation exists: Fixed an issue where the internal
translationvariable was initialized to an empty string. When the resolved remote and local paths were equal (i.e., no remote bundle for the locale), the lookup against the SDK's bundled.stringsfiles was skipped. When a remote translation did not exist, thetranslationvariable was not reset and consequently skipped the subsequent checks in the fallback lookup. Why it matters: Users viewing the Consent UI in locales served only by the SDK's bundled translations will now consistently see the correct localized strings, rather than the raw translation key. Additionally, if a translation key is missing from the remote bundle, both local and remote translation fallbacks will be handled appropriately.
Known Issues
- None at this time.
3.6.6
March 27, 2026