# A/B-тестирование интерфейса в iOS

В этом сценарии сервер выбирает вариант кнопки перехода к оплате, а приложение рисует его по `content.custom?.json`. Сначала [инициализируйте SDK](./configuration.md). Методы выбора контента сами не показывают UI; ограничения автопоказа описаны в [Своём UI и Custom JSON](./custom_ui.md).

Для ручного учёта показа отключите неявный impression в Options после инициализации:

```swift
GravitySDK.instance.setOptions(
    options: Options(isImplicitImpression: false),
    contentSettings: nil,
    proxyUrl: nil
)
```

Настройка глобальная. Если приложение уже задаёт ContentSettings или proxyUrl, передайте их здесь вместо nil; остальные Options тоже сохраните явно. Проверьте tracking показов кампании по фактическому ответу сервера.

## Подготовьте кампанию

1. Создайте [кампанию в приложении](/personalization/Campaigns/app_campaigns/getting_started.md) с двумя вариантами и нужным распределением аудитории для A/B-теста.
2. Настройте кампанию с Custom JSON для selector `checkout_button`, контекстом `CART` и location `app://cart`. Для примера используйте один корневой content без дополнительных шагов (`step` отсутствует).
3. В контрольном варианте задайте JSON:

```json
{ "variant": "control", "label": "Перейти к оплате" }
```

4. Во втором варианте задайте JSON:

```json
{ "variant": "compact", "label": "Оформить заказ" }
```

5. Опубликуйте кампанию. Названия `variant` и типы полей должны совпадать с моделями приложения. Настройте цель по бизнес-событию `checkout-started-v1`, которое отправляется при нажатии кнопки.

Приложение не распределяет аудиторию случайным образом: оно использует вариант из ответа сервера. Повторный выбор контента выполняйте при новом открытии размещения или явном обновлении, а не при каждом пересчёте UI.

## Получите и проверьте вариант

Добавьте в проект [gravity_content.swift](./examples/gravity_content.swift) и [ab_example.swift](./examples/ab_example.swift). Первый файл загружает корневой content и проверяет JSON; второй показывает кнопку и отправляет engagement. Модель принимает только `control` и `compact`, непустой `label` и корректные типы полей.

```swift
let cartContext = PageContext(type: .cart, data: [], location: "app://cart")

CheckoutExperimentView(pageContext: cartContext, onCheckout: openCheckout)
    .id(placementKey)
```

`placementKey` — Hashable-ключ размещения, которым управляет приложение. Меняйте его при смене контекста, чтобы создать отдельное состояние загрузки. Сам `PageContext` в iOS SDK имеет Equatable, но не Hashable и не подходит для `.id(...)`.

## Fallback

Если нет кампании, запрос завершился ошибкой, JSON повреждён, variant неизвестен или label пуст, показывайте стандартную кнопку приложения. Пример не связывает такой fallback с `campaign`/`content` и не отправляет ему impression выбранного варианта.

Не считайте fallback контрольной группой теста: контрольный вариант должен прийти из опубликованной кампании. Сбой выбора контента — отдельное состояние интеграции.

## Учтите фактический показ

`getContentBySelector` автоматически отправляет content load tracking. Это не подтверждение показа кнопки. Пример отправляет `ContentImpressionEngagement` только после появления UI с успешно разобранным вариантом и сохраняет исходные campaign/content для аналитики.

Показ в примере означает появление компонента в UI, без проверки перекрывающих слоёв. Если нужно учитывать видимость в scroll-контейнере, реализуйте detector и отправляйте `ContentVisibleImpressionEngagement` по [правилам собственного UI](./custom_ui.md#аналитика-собственного-ui). Не отправляйте impression ещё раз из callback загрузки.

При нажатии кнопки отдельно отправляется `CustomEvent` типа `checkout-started-v1` с текущим PageContext. Публичного `ContentClickEngagement` у native SDK нет. Результат теста оценивайте по выбранной бизнес-цели, а не по числу загрузок JSON.

## Проверка

1. Убедитесь, что оба серверных варианта отображаются с ожидаемым label и отступами.
2. Проверьте пустой ответ, ошибку сети, неизвестный variant и некорректные типы JSON: стандартный UI должен работать.
3. Проверьте, что загрузка не учитывается как ручной показ, а повторный пересчёт UI не создаёт новый выбор варианта.
4. Проверьте бизнес-событие после нажатия и отсутствие impression кампании для fallback.
