# Кампании и рекомендации в iOS

Сначала [подключите SDK](./overview.md) и опубликуйте [кампанию в приложении](/personalization/Campaigns/app_campaigns/getting_started.md). Навигация по URL/deeplink реализуется в приложении через [callback](./configuration.md).

## In-App кампании

SDK автоматически показывает кампании с `deliveryMethod`:

- `fullScreen`
- `modal`
- `bottomSheet`
- `snackBar`

Что требуется для автопоказа:

- подходящая активная кампания на backend;
- корректный [`PageContext`](./events.md#pagecontext);
- доступный `UIViewController` в момент показа после загрузки и задержки кампании;
- заполненный `variables.elements` в ответе и снятая presentation lock.
Блокировка новых in-app показов описана в [Конфигурации](./configuration.md#блокировка-показа-in-app-кампаний).

## Inline-компоненты

iOS SDK предоставляет компоненты для загрузки по selector и группе:

- `GravityInlineSwiftUIView` — для SwiftUI-экранов;
- `GravityInlineView` — для UIKit-экранов, включая ячейки `UITableView`;
- `GravityInlineListView` — горизонтальный список блоков из группы кампаний для UIKit.

SwiftUI:

```swift
GravityInlineSwiftUIView(
    selector: "homepage-recommendations",
    pageContext: PageContext(
        type: .homepage,
        data: [],
        location: "app://homepage"
    )
)
```

UIKit:

```swift
let inlineView = GravityInlineView(selector: "homepage-recommendations")

inlineView.initialize(
    pageContext: PageContext(
        type: .homepage,
        data: [],
        location: "app://homepage"
    )
)
```

Что важно учесть:

- `selector` должен совпадать с selector кампании на backend;
- `PageContext` должен соответствовать таргетингу кампании;
- если backend не вернул подходящий контент, компонент останется пустым;
- для UIKit-компонента нужно корректно настроить constraints и вызвать `initialize(pageContext:)`;
- высота inline-блока может обновляться по `frameUI.container.style.size.height`, если это значение пришло с backend.

Оба одиночных компонента поддерживают loader:

```swift
GravityInlineSwiftUIView(selector: "homepage-recommendations", pageContext: pageContext) {
    ProgressView()
}

inlineView.initialize(pageContext: pageContext) {
    ProgressView()
}
```

Для группы кампаний:

```swift
let listView = GravityInlineListView(groupSelector: "homepage-group")
listView.initialize(pageContext: pageContext) {
    ProgressView()
}
```

У UIKit list-компонента высота берётся из максимального `frameUI.container.style.size.height` среди загруженных блоков. Если ни у одного блока высота не задана, SDK устанавливает `0`; задайте размер в кампании.

После успешной загрузки эти компоненты сохраняют полученный контент. Изменение `PageContext` на том же экземпляре не гарантирует новый запрос: UIKit-компоненты прекращают загрузку, когда контент уже сохранён, а SwiftUI-компонент загружает его в `onAppear`. Для смены товара/контекста создайте новый UIKit view или измените identity SwiftUI view через `.id(...)`. Публичных методов сброса inline-кэша, как в Android SDK, здесь нет.

Встроенный iOS рендерер отображает товарный контейнер только в layout `row`; для `grid` он возвращает EmptyView. Если нужна сетка, используйте [собственный рендерер](./bdui.md).

## Карточки товаров

`ProductViewBuilder` нужен для отображения карточек товара в product-based или recommendation-блоках. Это обязательная часть интеграции товарных рекомендаций: приложение должно передать SDK шаблон карточки, иначе SDK не знает, какой UI использовать для товара в вашем приложении.

SDK получает блок рекомендаций, управляет контейнером, списком слотов и tracking-контекстом, но сам шаблон товарной карточки задает приложение. Разработчик реализует `ProductViewBuilder` или `LegacyProductViewBuilder` и передает его в `GravitySDK.initialize(...)` через параметр `productViewBuilder`.

После этого SDK использует переданный шаблон для каждого товара в блоке: вызывает builder для каждого `slot` и передает в него `slot`, `content` и `campaign`. Поэтому карточка отображается как native UI приложения, а не как готовая карточка, нарисованная SDK.

`slot` содержит данные одного рекомендованного товара:

- `slot.item` - словарь с полями товара из фида или из запрошенных `fields`: например `sku`, `name`, `price`, `old_price`, `image_url`, `url`, `brand`, `currency` и другие кастомные поля;
- `slot.strId` и `slot.slotId` - идентификаторы позиции товара в выдаче;
- `slot.fallback` - признак, что товар пришел из fallback-логики;
- `slot.events` - tracking-данные товара, которые SDK использует при отправке engagement.

Названия ключей в `slot.item` должны совпадать с вашим фидом и настройкой `fields`. Например, если в ответе приходит `imageUrl`, читайте `imageUrl`; если используется поле фида `image_url`, читайте `image_url`.

Сигнатуры протоколов — в [справочнике API](./api_reference.md#ui-компоненты).

Пример шаблона карточки:

Полный пример: [product_view_builder.swift](./examples/product_view_builder.swift). Реализуйте openProduct и передайте MyProductBuilder в initialize. Изображения через SwiftUI.AsyncImage доступны с iOS 15; для iOS 14 подключите свой загрузчик.

Что важно учесть:

- `ProductViewBuilder` получает `slot`, `content` и `campaign`, поэтому карточка товара может учитывать контекст кампании и content block;
- если в приложении уже есть UIKit-карточка товара, используйте `LegacyProductViewBuilder` и верните `UIView` из `createView(...)`;
- SDK автоматически отслеживает visible impression товара во встроенном блоке, а tap по кастомной карточке приложение отправляет вручную через `sendProductEngagement(...)` перед навигацией;
- `productFilter` применяется к `Products.slots` при декодировании ответа SDK. Используйте его только для простого клиентского исключения слотов, когда это действительно нужно в приложении.

## Кто отправляет engagement

Успешная загрузка автоматически отправляет content load tracking. Встроенные UI-компоненты отправляют показы контента, видимость товаров и tracking для поддерживаемых действий. Карточка из ProductViewBuilder уже обёрнута detector SDK: вручную отправляйте её клик перед навигацией, не дублируйте visible impression.

Ручные вызовы engagement не генерируют tracking callback. Для собственного UI используйте [отдельные правила аналитики](./custom_ui.md#аналитика-собственного-ui).

iOS учитывает не менее 50% площади в окне сразу, один раз на экземпляр detector.
