> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kontext.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Guides

> Patterns and recipes for common Swift SDK integration tasks.

## Handling no-fill

Subscribe to `.noFill` to know when no ad was returned (geo-restriction, frequency cap, etc.). The `skipCode` payload tells you why.

```swift theme={null}
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.

```swift theme={null}
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.

```swift theme={null}
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.

<Warning>
  `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.
</Warning>

## Live-updating consent mid-session

When the user updates their consent in your CMP, call:

```swift theme={null}
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:

```swift theme={null}
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.

```swift theme={null}
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](/concepts/pacing) for the full pattern.

```swift theme={null}
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:

```swift theme={null}
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) {}
}
```
