Changelog
Version history and release notes for the iOS SDK.
Version 1.0.0 (September 15, 2026)
1.0.0 is now stable and no longer in beta. Update your dependency to the stable release. Upgrading from 0.x? Follow the migration guide.
Version 1.0.0-beta.1 (August 25, 2026)
First release of the 1.0 line — the API the SDK stabilizes on for GA — published as a beta on Swift Package Manager; pin the exact version. The full 0.x → 1.0 upgrade path is in the migration guide.
Highlights
- Impressions now require the ad to be on screen. An impression is billed only once the card has rendered and at least one visible pixel of it is inside the host window — surviving any ancestor that clips — instead of at first layout, which billed cards laid out below the fold. Expect impression counts to step down (and CTR per impression to step up) in layouts where ads render off screen or clipped: feeds,
LazyVStacks, long transcripts. The separatead_visibleviewability signal (50% visible for 1 second) reads the same clip-aware geometry. No integration changes. - Every request now sends a real
User-Agent, so device and OS classification is accurate downstream — macOS hosts are no longer misclassified as iOS phones, and iPad now reports as a tablet. - Expanded privacy manifest.
PrivacyInfo.xcprivacynow also declares Coarse Location and Other Diagnostic Data, and adds the Third-Party Advertising purpose to the existing declarations;NSPrivacyTrackingstaysfalse. Review your App Privacy answers when you upgrade — see data collection. - Event reporting is stamped at the interaction. Each event now carries a unique id and the moment the tap, view, or dismissal actually occurred, so analytics measure the user rather than the network hop.
- Under-13 age values are rejected. A stored under-13 age range is discarded and purged rather than sent — see Children's privacy.
Breaking changes
- The legacy V1 query surface is removed:
Koah.shared.prefetch(query:queryResponse:messageId:conversationId:),resolveAd(query:queryResponse:messageId:conversationId:), andcancelPrefetch(messageId:). UseprefetchAd(adContext:cacheKey:),requestAd(adContext:cacheKey:), andcancelPrefetchAd(cacheKey:); every ad fetch now goes through the unified V2 path. KoahAdLoaderis removed (deprecated in 0.10.0). PairprefetchAd(adContext:cacheKey:)withKoahCard(cacheKey:)instead.- The deprecated
KoahCard(adContext:overrideMaxWidth:overrideMaxHeight:cacheKey:)initializer is removed. Bound slots per axis withwidth/height(KoahAxisSize).
Version 0.10.0 (August 12, 2026)
Highlights
- Koah's servers can now pick which small image-card treatment a fill renders: today's image-leading control, a labelled action button, a trailing square image, or a content-first layout with a full-height 1.91:1 creative, inline advertiser identity, one headline, and the existing chevron. The selection arrives with the ad; anything this SDK doesn't recognise renders the control. Every fill keeps rendering today's image-leading control until the selection is turned on server-side, so this release changes nothing on its own. Your integration is unchanged: no new format names appear in the ad's
format, no new content fields were added, and the SDK gains no new dependency, configuration, or setup step. - Demographic targeting: gender and household income. Call
Koah.shared.setGender(.female)andKoah.shared.setIncomeBracket(.income50kTo100k)(brackets are annual household income in thousands of USD). Values are persisted locally and uploaded for ad targeting, joining the existingsetAge/setAgeRange. Pass.unknownto stop this device storing and re-sending a value — a local clear only; it does not delete a value already sent to Koah.
Improvements
KoahEvent.impressionTrackednow fires only when the impression was accepted by Koah. Previously it fired on every render regardless of the outcome, so a transient failure reported an impression Koah had not recorded. Publishers who count this callback should expect their totals to line up with Koah's reporting rather than with render counts. Matches Android, Flutter and React Native.- Rebuilt impression visibility tracking. The tracker now actively resamples while a card is on screen and resets its dwell clock when the card drops below the visibility threshold, so fast scrolls, covered cards, and half-visible slots can no longer accrue toward an impression they shouldn't.
- Fixed a first-
configurecrash and a class of init-order failures. A blankpublisherIdis now rejected only on the firstconfigure; a duplicate call — even one carrying a transiently blank id — warns and no-ops instead of taking down a host that was already correctly configured.setAgeno longer crashes on out-of-range values, and age ranges are transmitted correctly. card_text_mdno longer de-renders under large Dynamic Type. Custom-font rendering is pinned to SDK-locked sizes, so accessibility text settings can't push a text card past its budget and blank the slot.- Slot-layout fixes for lazy containers. Cards inside
LazyVStackno longer starve their fetch, and transient zero-size layout passes no longer emit a spuriousslotTooSmall. - Expandables are never bricked and scroll properly. A failed expanded-content fetch now retries on the next tap and falls back to the CTA link; the expanded body scrolls within height-capped slots and fades its bottom edge until fully scrolled.
- Image cards recover from zero-width first layouts. The
card_image_mdhero image re-arms its load when the card's width changes, so a zero-width first pass can no longer latch the hero grey. - Small-card layout: advertiser icon leads, ad image trails. The small card now leads with the advertiser icon for clearer sponsorship attribution, with the creative thumbnail at the trailing edge (matches Android).
ad_clickedevents now carry tap position and viewport geometry (tap point, ad frame, window size, and display scale, all in points), improving click-quality signals with no integration changes.- Malformed fills with null image metadata now render gracefully instead of being dropped.
KoahAdLoaderis deprecated. PairKoah.prefetchAd(adContext:cacheKey:)withKoahCard(cacheKey:)instead.- Removed the unreleased Category Rail and Photo Stack formats before any stable release shipped them.
API changes
⚠️ Removed
- The server-decoded ad-content model is no longer public (
KoahAd,KoahAdvertiser,KoahProduct, wire payloads, …) — the supported path returnsKoahAdResultand renders viaKoahCard/components, with the publicKoahSampleAdfamily replacing raw-model fixtures. Breaking. KoahAdSizeConstraints— internal render-envelope constants, now internal; the public sizing contract is the slot-bounds model. Breaking.AdSourceand thesource:parameter onrequestAd/prefetchAd— public requests always used the direct path; the parameter is gone. Breaking.KoahError— the branded error enum is gone; the only error a normal integrator receives isKoahAdResult.error(message:fromCache:), and platform-native error idioms are used elsewhere. Breaking.Koah.clearCache()/Koah.isInitialized— now internal. Breaking.- Category Rail and Photo Stack — the unreleased preview APIs, wire fields, and renderers were withdrawn before their first stable release; released consumers lose no API.
⚠️ Changed
- The size/capability API is gone —
KoahAdSize,KoahAdOptions, and everysize-takingrequestAd/prefetchAd/card overload are removed; pass slot dimensions (or omit for an unbounded slot). Breaking. KoahCardsizing is per-axis — newwidth/heightparameters of typeKoahAxisSize(.fixed,.range(min:max:),.fill;nil= unbounded) replaceoverrideMaxWidth/overrideMaxHeight(the old initializer remains with a deprecation warning), andcard_text_mdgains a text-length budget so over-long copy de-renders instead of clipping. Breaking.KoahAdResultis a discriminated union —.filled(fromCache:)/.noFill(fromCache:)/.error(message:fromCache:)replace the oldfilledflag, so success, no-fill, and failure are distinguishable; the result no longer carriesformat. Breaking.- Logging config is
logLevel: KoahLogLevel— thedebug: Boolflag is replaced by one shared enum (.none/.error/.warning/.info/.debug;.noneis the default). Breaking. - Preview requests are
requestPreviewAd(previewFormat:cacheKey:)— renamed from therequestAd(previewFormat:)overload; the context-basedrequestAdis unchanged. Breaking. reset()is no longer public — teardown is unified and internal on all four SDKs. Breaking.
Deprecated
KoahAdLoader— pairprefetchAd(adContext:cacheKey:)withKoahCard(cacheKey:)instead. (Removed in 1.0.)
Added
KoahSampleAdfamily — hand-constructable public specimens (KoahSampleAd, advertiser/product/image/poll sub-types, ready-made.card/.image/.poll/.expandable) for previews, demo galleries, and snapshot fixtures without a network fetch.KoahEvent.inAppBrowserClosed— the in-app-browser dismissal, already tracked internally, now reaches the consumeronEventcallback (parity with Android).setGender(_:)/setIncomeBracket(_:)— publisher-provided demographic targeting alongside the existing age dimension.
Version 0.9.1 (June 23, 2026)
Highlights
- Ad disclaimers. When an ad carries disclaimer text,
KoahCardnow shows an info (ⓘ) affordance next to the "Sponsored" label that opens a disclaimer sheet. Surfaced on the model as the new optionalKoahAd.disclaimer; the affordance renders automatically with no integration changes.
Improvements
- Advertiser icons render more gracefully. Icons now show a skeleton while loading, crossfade in on success, and fall back to a vector placeholder mark when there's no icon URL or the image fails to load.
/client/infouploads are debounced. Device info is now uploaded only when a stable fingerprint changes since the last successful send, instead of on every launch — fewer redundant network calls.
Version 0.9.0 (June 4, 2026)
Highlights
- Pixel-precise ad sizing with
KoahDimensions.requestAd/prefetchAdandKoahCardnow take explicit slot constraints — passoverrideMaxWidth/overrideMaxHeight(in points) onKoahCard, or aKoahDimensions(maxWidth:maxHeight:)value on the request, to bound an ad to your layout. Each axis is independent; omit both for an unbounded slot. Replaces the coarse t-shirt-size buckets with exact dimensions.
Improvements
sizedeprecated in favor of dimensions. TheKoahAdSizet-shirt-bucketsize:parameter onrequestAd/prefetchAdnow emits a deprecation warning. Migrate tooverrideMaxWidth/overrideMaxHeightonKoahCard(orKoahDimensionson the request); omit for unbounded.sizewill be removed in a future release.
Version 0.8.1 (June 2, 2026)
Improvements
- Unified impression & visibility tracking. Reworked internal visibility bookkeeping so impression and viewed events follow the same gating semantics across iOS, Android, Flutter, and React Native. Event timing for partially-occluded or de-rendered cards is now consistent with the other SDKs.
Version 0.8.0 (May 29, 2026)
Highlights
- Ad report flow. Tapping the new
…button onKoahCard's caption opens a half-sheet (About this ad→Report this adwith five reasons + Submit) that posts toPOST /k/report. No new public API surface and no opt-in — the affordance ships on every standardKoahCard. Sheet auto-adapts to system light/dark via the ambientKoahThemeand stays iOS 13+ compatible. - Optional
slotparameter.Koah.shared.requestAd(adContext:size:cacheKey:slot:)andprefetchAd(...)now accept an optionalslot: String?that surfaces in matching and reporting. Lets a single page tag multiple distinct ad placements without colliding on the cache key or muddying analytics. requestPreviewAd(previewFormat:)overload. NewKoah.shared.requestPreviewAd(previewFormat:)returns a deterministic sample creative for a given format (.small/.medium/.expandable/.poll), so publishers can wire their preview UIs without depending on live fill.- Optional
experimentTag. NewexperimentTag: String?parameter onrequestAd/prefetchAdis forwarded with every ad request, letting publishers split-test creatives or surface variants without piggy-backing on the cache key. - Poll CTA with animated reveal. Clickable poll CTA reveals the post-vote state with a word-by-word blur-reveal animation gated on 50% visibility, matching the expandable format's reveal pattern.
Improvements
adTextremoved from publisher-facing fill events. Tracking events no longer leak ad copy to the publisher's analytics surface.
Version 0.7.1 (May 18, 2026)
Bug Fixes
- Ad images now render on the V2 endpoint.
KoahAdwas decoding the image off a JSON key the V2/ad_requestsserver response doesn't emit, so image-bearing creatives rendered as text-only. The wire mapping is fixed and any ad with aKoahResponsiveImagepayload now displays the image insideKoahCard.
Version 0.7.0 (May 12, 2026)
Highlights
- Unified
requestAdAPI (v2). NewKoah.shared.requestAd(adContext:size:cacheKey:),Koah.shared.prefetchAd(adContext:size:cacheKey:), andKoah.shared.cancelPrefetchAd(cacheKey:), plus a publicKoahAdContextenum (.conversation/.article/.feed/.static) and aKoahAdResult(filled, fromCache, format)return type. Replaces the per-surfaceprefetch/resolveAdprimitives with a discriminated context the server uses for matching, copy selection, and format eligibility — same shape as Android, React Native, and the upcoming Flutter SDK. Migration:Koah.shared.prefetch/resolveAd/cancelPrefetchstill work but emit deprecation warnings; new code should adoptrequestAd/prefetchAd. KoahCard(adContext:size:cacheKey:)initializer (breaking). Replaces the primaryKoahCard(query:queryResponse:messageId:isResponseReady:)initializer.messageId:is renamed tocacheKey:across all initializers. Cache-warmed cards now fireonEvent(.adServed)consistently — analytics no longer under-count serves that came from a priorprefetchAd.KoahFontstyling type (breaking).KoahTheme.fontFamily: Font?is replaced withKoahTheme.font: KoahFont?. Callers get explicit control over typeface, weight, andFont.Designwhile the SDK locks per-element sizes. UseKoahFont.system(weight:design:)orKoahFont.custom(_:weight:design:). Migration:fontFamily: yourFont→font: .custom("YourFont", weight: .regular).- Expiry-aware ad cache. Cached entries honor the server's
expires_at, evicted on read once stale, so a long-livedKoahinstance doesn't keep replaying ads past their expiry.
Improvements
KoahFetcherlogging. Malformed/ad_requestsresponses log asMalformed /ad_requests response: …instead of being lumped in with transport failures, so server-shape regressions surface in debug builds instead of looking like no-fills.
Version 0.6.0 (April 2026)
Highlights
- New
KoahThemestyling API (breaking). Replaces the ~17-knobKoahCardStylewith a minimal 4-knobKoahTheme(colorScheme,accentColor,fontFamily,cornerRadius) applied once at the root via.koahTheme(_:)and propagated through SwiftUI's@Environment. Auto dark mode flips with the system color scheme; text-on-accent is auto-contrasted via WCAG luminance. Mirrors the AndroidKoahThemerefactor. Migration:KoahCardStyle,KoahCardOptions,KoahCardSize,KoahCardSizeConstraints, andKoahCardWidthBehaviorare removed — callers move to.koahTheme(...)+KoahAdOptions(size:), with width controlled via SwiftUI's native.frame(...). A newchrome: .bareparameter onKoahCardprovides the borderless variant. - Prefetch / resolveAd primitives for feeds. New
Koah.prefetch,Koah.resolveAd,Koah.cancelPrefetch,KoahAdState,KoahAdLoader, and aKoahCard(messageId:)initializer let SwiftUI feed integrations resolve ad state before rendering. (Superseded by the v2requestAdAPI in 0.7.0.) - Medium card size enforcement.
KoahCardSizeConstraints(220–1000 width, 280–1000 height) is now enforced at measure time via the newKoahMinSizeProbeSwiftUILayout. BelowminWidth, or when content would clip belowminHeight, the card collapses to zero size and logs once. iOS 16+ only; older OS versions fall through as a no-op.
Improvements
- Debug warning on no-fill with a production key. Debug builds now log a loud, unsilenceable warning when the server returns no-fill on a non-demo publisher key, pointing integrators at demo keys for reliable local testing.
- Expanded
/client/infopayload. The SDK now sendsdeviceModel,ipAddress,userAgent, andisLocationEnabled. Locale normalized from underscore to hyphen format (en-US) to match Android and Flutter.
Version 0.5.2
Bug Fixes
- Fixed URLSession blocked by iOS tracker protections. The SDK's privacy manifest previously declared
app.koah.aias anNSPrivacyTrackingDomainwithNSPrivacyTracking: true, causing iOS 17+ to hard-block all Koah API calls under Limit IP Address Tracking (default ON). The declaration did not match actual behavior — Koah's backend doesn't correlate device IDs across publisher apps and the SDK never reads IDFA. The manifest has been corrected:NSPrivacyTrackingand per-data-typeNSPrivacyCollectedDataTypeTrackingare nowfalse, andNSPrivacyTrackingDomainshas been removed.
Version 0.5.1
Bug Fixes
- Fixed small cards never rendering.
KoahCardOptions.sizeis now forwarded to the server on/query. Previously the SDK only usedsizefor a client-side strict-match after the fetch, so the server always returned a medium ad and small-slot requests were dropped as no-fill.
Version 0.5.0
Highlights
- Compact small card format. A new
KoahCardSizeoption onKoahCardOptionslets integrators render the compact "small" card variant, matching the format already shipping on Android and web. Includes a custom marquee treatment with edge fade and reduced-motion fallback. - Age range visitor attributes. Publishers can now pass demographic signals for higher RPMs via the new
Koah.setAgeRange(_:)andKoah.setAge(_:)APIs. Values persist across launches and auto-resend on SDK init.
Version 0.4.1
Bug Fixes
- Fixed stale client ID when publisher ID changes. The SDK now validates that the cached client ID belongs to the current publisher before reusing it. Previously, switching publisher IDs (e.g. between environments) could send requests with the old publisher's client ID, causing attribution to the wrong account.
Version 0.4.0
Highlights
- New ad formats: Expandable and Poll.
KoahExpandableCardshows a tappable card that expands inline to reveal richer ad content, andKoahPollCardrenders an interactive poll the user can vote on directly. Both are first-class SwiftUI views that fit alongsideKoahCard. - KoahCard visual refresh. Layout, spacing, typography, and color treatments now match the latest cross-platform design (parity with Android, Flutter, and React Native).
- App lifecycle tracking. The SDK now reports foreground/background transitions via
KoahTrackingEvent, matching the other platform SDKs. - macOS support. The XCFramework now ships a macOS slice in addition to iOS device and simulator.
Improvements
- Reliable impression tracking. The visibility tracker has been rewritten with a UIKit-backed
VisibilityProbeand a Combine pipeline that accounts for safe area insets,isHidden, and alpha. Impressions now fire correctly even when a card starts below the fold and is scrolled into view. - Unified version management. The SDK version is now sent in API request headers, letting the server segment behavior by SDK version.
Was this helpful?