Handling no-fill
Subscribe to .noFill to know when no ad was returned (geo-restriction, frequency cap, etc.). The skipCode payload tells you why.
onEvent: { event in
if case .noFill(let data) = event {
print("no fill:", data.skipCode)
}
}
Sizing ads in UIKit lists
Inside UITableView / UICollectionView cells, observe InlineAdUIView.onHeightChange and trigger a row resize when it fires. The height changes as the ad streams in and stabilizes at the final value.
let adView = InlineAdUIView(ad: ad)
adView.onHeightChange = { [weak self] height in
self?.updateRowHeight(height)
}
Live-updating session options
A subset of session options can be live-updated without recreating the session. Updates are read on the next /preload, so changes take effect on the next user message.
session.updateOptions(MutablePublisherOptions(
variantId: "new-variant"
))
Live-updateable fields: variantId, regulatory, userEmail, advertisingId, vendorId. Non-nil fields overwrite; nil fields are left unchanged. To clear a field, recreate the session.
publisherToken, userId, conversationId, enabledPlacementCodes, and character are not live-updateable. Changing them mid-session would desync the /init registration or leave accumulated message history targeted at the wrong persona. Recreate the session instead.
Live-updating consent mid-session
When the user updates their consent in your CMP, call:
session.updateOptions(MutablePublisherOptions(
regulatory: Regulatory(gdpr: 1, gdprConsent: "<new-TCF-string>")
))
The next preload picks up the new value — no session recreation needed.
Switching character
The active character cannot be live-updated — the accumulated message history belongs to the original persona, so swapping mid-session would leave messages targeted at the wrong character.
To switch character, destroy the current session and create a new one:
session.destroy()
session = KontextAds.createSession(SessionOptions(
publisherToken: "<your-publisher-token>",
userId: "user-1234",
conversationId: "conv-new",
character: newCharacter
))
The same applies to publisherToken, userId, conversationId, and enabledPlacementCodes — recreate the session whenever any of those change.
Loading older messages (conversation restore)
When restoring a conversation from your backend, call addMessage(_:) once per historical message in order. Preloads are debounced by 10 ms, so rapid sequential calls coalesce into a single preload for the most recent user message — you won’t fire one preload per restored message.
for historicalMessage in loadedFromBackend {
session.addMessage(Message(
id: historicalMessage.id,
role: historicalMessage.role,
content: historicalMessage.content,
createdAt: historicalMessage.timestamp
))
}
Pass every message, suppress ads with trackOnly
Always feed every message into the session, even when you don’t want an ad to appear (e.g. when a user is on a free trial, in a no-ads region, or you’ve decided to show ads only every Nth message). Skipping addMessage(_:) calls breaks the conversation context the server relies on for targeting. See Pacing for the full pattern.
let shouldShowAd = userMessageCount.isMultiple(of: 5)
session.addMessage(
Message(id: msg.id, role: .user, content: msg.content, createdAt: Date()),
options: AddMessageOptions(trackOnly: !shouldShowAd)
)
When trackOnly: true, the preload still fires (server keeps full analytics) but no .filled event will arrive for that message and session.createAd(...) won’t resolve to an ad.
SwiftUI
The SDK ships UIKit views only. Wrap InlineAdUIView in a UIViewRepresentable to embed it in SwiftUI:
import SwiftUI
import KontextSwiftSDK
struct InlineAd: UIViewRepresentable {
let ad: Ad
let onHeightChange: (CGFloat) -> Void
func makeUIView(context: Context) -> InlineAdUIView {
let view = InlineAdUIView(ad: ad)
view.onHeightChange = onHeightChange
return view
}
func updateUIView(_ uiView: InlineAdUIView, context: Context) {}
}