# Настройка Pushwoosh InboxKit для iOS

*Доступно с iOS SDK [7.0.40](https://github.com/Pushwoosh/pushwoosh-ios-sdk/releases/tag/7.0.40).*

Pushwoosh InboxKit предоставляет современный экран входящих сообщений на базе UIKit поверх существующего бэкенда для входящих сообщений. Шесть стандартных макетов ячеек охватывают распространённые формы карточек контента — от простых баннеров до каруселей изображений, встроенного видео и пропусков Apple Wallet. Встроенные CTA-кнопки обрабатывают самые распространённые взаимодействия, а весь интерфейс открыт для создания подклассов, если вам нужен индивидуальный дизайн.

<img src="/setting-up-pushwoosh-inboxkit-ios-feed.webp" alt="Лента InboxKit с карточками баннер, с подписью, классическая, карусель, видео и Apple Wallet" width="300" style="display: block; margin: 0 auto;"/>

<p style="text-align: center; opacity: 0.7; font-size: 0.875rem; margin-top: 0.5rem;">Стандартная лента InboxKit с карточками баннер, с подписью, классическая, карусель, видео и Apple Wallet.</p>

## Когда использовать InboxKit

Используйте InboxKit для любой новой интеграции с iOS. Это рекомендуемая замена для устаревшего модуля [PushwooshInboxUI](/ru/developer/pushwoosh-sdk/ios-sdk/setting-up-pushwoosh-inboxui-ios/) на Objective-C.

InboxKit предоставляет вам:

- Шесть встроенных типов ячеек — баннер, с подписью, классическая, карусель, видео и Apple Wallet — выбираются для каждого сообщения через `displayType` в полезной нагрузке или принудительно устанавливаются из кода через `attributes.forceCellKind`. Полный список см. в разделе [Типы карточек](#типы-карточек). (Карточка Apple Wallet доступна только на iOS.)
- Встроенные CTA-кнопки с типизированным перечислением `PushwooshInboxButtonAction` (`openURL`, `dismiss`, `markRead`, `custom`). SDK автоматически обрабатывает первые три. Ваш делегат направляет `custom` в вашу собственную логику.
- Поддержка закрепления: сообщения с `actionParams["pinned"] == true` поднимаются в верхнюю часть ленты и отображают значок булавки.
- Свайп для удаления, потяните для обновления, автоматическая отметка как прочитанное при исчезновении — всё это можно переключать через `PushwooshInboxKitAttributes`.
- Постоянное хранилище: удаления и состояние прочтения сохраняются после перезапуска процесса, даже если сетевой вызов ещё не был подтверждён.
- Открытый базовый класс `PushwooshInboxCell` для полностью настраиваемых макетов.

Серверный контракт не изменился — тот же бэкенд для входящих сообщений Pushwoosh, полезные нагрузки и инструменты панели управления работают, как и раньше.

## Выберите способ интеграции

- [**Настройка InboxKit с помощью Swift Package Manager**](/ru/developer/pushwoosh-sdk/ios-sdk/setting-up-pushwoosh-inboxkit-ios/set-up-inboxkit-with-swift-package-manager/) — рекомендуется для новых проектов.
- [**Настройка InboxKit с помощью CocoaPods**](/ru/developer/pushwoosh-sdk/ios-sdk/setting-up-pushwoosh-inboxkit-ios/set-up-inboxkit-with-cocoapods/) — для проектов, уже использующих CocoaPods.

## Типы карточек

InboxKit выбирает макет ячейки для каждого сообщения. Стандартный резолвер читает `displayType` из полезной нагрузки пуша — поместите его в объект `data`, который SDK доставляет в `actionParams`. Если `displayType` отсутствует, резолвер использует эвристику: изображение без заголовка → баннер, изображение с заголовком → с подписью, иначе классическая. Чтобы принудительно задать один макет для всей ленты из кода, установите `attributes.forceCellKind`.

Каждый расширенный макет деградирует корректно: если требуемая полезная нагрузка отсутствует или повреждена, карточка переключается на `classic` вместо пустого плейсхолдера (в лог записывается `WARN`).

| `displayType` | Макет | Обязательное поле полезной нагрузки | Деградирует до |
|---|---|---|---|
| `banner` | Изображение на всю ширину, без текста | image (`inbox_image` или `data.image`) | `classic`, если нет изображения |
| `captioned` | Изображение сверху, заголовок и текст ниже | image (`inbox_image` или `data.image`) | `classic`, если нет изображения |
| `classic` | Цветной аватар с инициалом + заголовок + текст | — | — |
| `carousel` | Галерея из нескольких изображений со свайпом | `data.carousel` (массив слайдов) | `classic`, если нет слайдов |
| `video` | Постер с значком воспроизведения, полноэкранный плеер по нажатию | `data.video` (`url` + необязательный `poster`) | `classic`, если нет дескриптора |
| `wallet` | Кнопка «Добавить в Apple Wallet» (только iOS) | `data.wallet` (URL `.pkpass`) | `classic`, если нет URL пропуска |

{/* TODO(screenshot): replace each placeholder below with a per-card screenshot. Suggested filenames: setting-up-pushwoosh-inboxkit-ios-card-banner.webp, -captioned.webp, -classic.webp, -carousel.webp, -video.webp, -wallet.webp — then swap each <div> for an <img src="/<filename>" width="240" .../>. */}
<div style="display: flex; flex-wrap: wrap; gap: 1rem; justify-content: center; margin: 1.5rem 0;">
  <figure style="width: 200px; margin: 0;"><img src="/setting-up-pushwoosh-inboxkit-ios-card-banner.webp" alt="Карточка InboxKit типа баннер" style="width: 100%; border-radius: 12px;"/><figcaption style="text-align: center; font-size: 0.85rem; color: #94a3b8; margin-top: 0.4rem;"><strong>Карточка-баннер</strong></figcaption></figure>
  <figure style="width: 200px; margin: 0;"><img src="/setting-up-pushwoosh-inboxkit-ios-card-captioned.webp" alt="Карточка InboxKit с подписью" style="width: 100%; border-radius: 12px;"/><figcaption style="text-align: center; font-size: 0.85rem; color: #94a3b8; margin-top: 0.4rem;"><strong>Карточка с подписью</strong></figcaption></figure>
  <figure style="width: 200px; margin: 0;"><img src="/setting-up-pushwoosh-inboxkit-ios-card-classic.webp" alt="Классическая карточка InboxKit" style="width: 100%; border-radius: 12px;"/><figcaption style="text-align: center; font-size: 0.85rem; color: #94a3b8; margin-top: 0.4rem;"><strong>Классическая карточка</strong></figcaption></figure>
  <figure style="width: 200px; margin: 0;"><img src="/setting-up-pushwoosh-inboxkit-ios-card-carousel.webp" alt="Карточка InboxKit типа карусель" style="width: 100%; border-radius: 12px;"/><figcaption style="text-align: center; font-size: 0.85rem; color: #94a3b8; margin-top: 0.4rem;"><strong>Карточка-карусель</strong></figcaption></figure>
  <figure style="width: 200px; margin: 0;"><img src="/setting-up-pushwoosh-inboxkit-ios-card-video.webp" alt="Видеокарточка InboxKit" style="width: 100%; border-radius: 12px;"/><figcaption style="text-align: center; font-size: 0.85rem; color: #94a3b8; margin-top: 0.4rem;"><strong>Видеокарточка</strong></figcaption></figure>
  <figure style="width: 200px; margin: 0;"><img src="/setting-up-pushwoosh-inboxkit-ios-card-wallet.webp" alt="Карточка InboxKit Apple Wallet" style="width: 100%; border-radius: 12px;"/><figcaption style="text-align: center; font-size: 0.85rem; color: #94a3b8; margin-top: 0.4rem;"><strong>Карточка Apple Wallet</strong></figcaption></figure>
</div>

Карточки баннер, с подписью и классическая управляются стандартными полями сообщения (изображение, заголовок, текст) плюс необязательный массив `buttons` — см. [Добавление встроенных CTA-кнопок](#добавление-встроенных-cta-кнопок). Карточки карусель, видео и Apple Wallet содержат дополнительные структурированные данные внутри `data`, описанные ниже.

### Карточка-карусель

Карусель отображает несколько изображений из одного сообщения — галерею со свайпом с необязательными подписями и целевыми ссылками для каждого слайда. Слайды находятся в `data.carousel`. Каждый слайд требует `image`. Поля `title` (подпись поверх изображения) и `url` (deep link при нажатии) необязательны. Слайд без изображения пропускается. Нажатие на слайд без `url` передаётся стандартному действию строки.

```json title="POST https://api.pushwoosh.com/json/1.3/createMessage"
{
  "request": {
    "application": "XXXXX-XXXXX",
    "auth": "API_TOKEN",
    "notifications": [{
      "send_date": "now",
      "ios_title": "New arrivals",
      "content": "Swipe through this week's drops",
      "inbox_days": 7,
      "data": {
        "displayType": "carousel",
        "carousel": [
          { "image": "https://cdn.example.com/inbox/1.jpg", "title": "New in", "url": "myapp://product/1" },
          { "image": "https://cdn.example.com/inbox/2.jpg", "title": "On sale", "url": "myapp://product/2" },
          { "image": "https://cdn.example.com/inbox/3.jpg" }
        ]
      },
      "platforms": [1]
    }]
  }
}
```

### Видеокарточка

Видеокарточка показывает постер с значком воспроизведения. Нажатие открывает полноэкранный плеер (со звуком, даже при включённом беззвучном режиме). Дескриптор находится в `data.video`: `url` обязателен и должен быть потоком или файлом по `http`/`https`. `poster` — необязательное изображение предпросмотра.

```json title="POST https://api.pushwoosh.com/json/1.3/createMessage"
{
  "request": {
    "application": "XXXXX-XXXXX",
    "auth": "API_TOKEN",
    "notifications": [{
      "send_date": "now",
      "ios_title": "Watch the reveal",
      "content": "Tap to play",
      "inbox_days": 7,
      "data": {
        "displayType": "video",
        "video": {
          "url": "https://cdn.example.com/inbox/clip.mp4",
          "poster": "https://cdn.example.com/inbox/poster.jpg"
        }
      },
      "platforms": [1]
    }]
  }
}
```

### Карточка Apple Wallet

Карточка Apple Wallet показывает необязательное изображение, заголовок и текст над официальной кнопкой **Add to Apple Wallet**. Нажатие на кнопку загружает `.pkpass` и открывает системный лист добавления пропусков. Используйте её для доставки купонов, карт лояльности, билетов или посадочных талонов прямо из входящих. Карточка доступна только на iOS и Mac Catalyst. На других платформах сообщение отображается как классическая карточка.

URL пропуска находится в `data.wallet` — как строка или как объект с полем `pass`. Необязательный `data.image` добавляет изображение-герой. Кнопка скрывается автоматически, если нет URL пропуска или устройство не может добавлять пропуски.

```json title="POST https://api.pushwoosh.com/json/1.3/createMessage"
{
  "request": {
    "application": "XXXXX-XXXXX",
    "auth": "API_TOKEN",
    "notifications": [{
      "send_date": "now",
      "ios_title": "Your loyalty card is ready",
      "content": "Add it to Apple Wallet in one tap",
      "inbox_days": 7,
      "data": {
        "displayType": "wallet",
        "image": "https://cdn.example.com/inbox/loyalty.png",
        "wallet": "https://passes.example.com/v1/passes/pass.com.example.loyalty/abc123?token=…"
      },
      "platforms": [1]
    }]
  }
}
```

Результат передаётся вашему делегату:

```swift
extension MyInboxHost: PushwooshInboxKitDelegate {

    func inboxKit(_ vc: PushwooshInboxKitViewController,
                  didAddWalletPassFor message: PWInboxMessageProtocol) {
        // Пропуск теперь в Wallet пользователя — при желании покажите подтверждение.
    }

    func inboxKit(_ vc: PushwooshInboxKitViewController,
                  didFailToAddWalletPassFor message: PWInboxMessageProtocol,
                  error: Error?) {
        // Загрузка не удалась — покажите повтор, запишите в лог и т. д.
    }
}
```

Оба колбэка необязательны (имеют пустые реализации по умолчанию). Если пользователь отменяет системный лист, колбэк не вызывается — это ни успех, ни ошибка.

## Специальные возможности

Ячейки InboxKit готовы к VoiceOver из коробки. Карточки баннер, с подписью и классическая передают заголовок, текст и дату через базовые метки. Встроенные CTA-кнопки озвучивают собственные заголовки. Расширенные карточки добавляют явную семантику:

- **Видео** — постер представлен как единый элемент-кнопка с меткой «Play video» (traits `.button` + `.startsMediaSession`), поэтому VoiceOver объявляет его как медиаэлемент, а не как обычное изображение.
- **Карусель** — каждый слайд является элементом-кнопкой, чья accessibility-метка — подпись слайда или «Slide», если подписи нет. Индикатор страниц объявляет текущую позицию как «*n* из *total*».
- **Apple Wallet** — кнопка **Add to Apple Wallet** — это стандартная `PKAddPassButton` от Apple со своей локализованной меткой VoiceOver.

Для UI-тестирования и автоматизации заданы два стабильных `accessibilityIdentifier`: `inboxkit.video.play` на видеопостере и `inboxkit.wallet.add` на кнопке Wallet.

## Чтение пользовательских данных из сообщения

Чтобы пуш появился во входящих, запрос `createMessage` в [Messages API](/ru/developer/api-reference/messages-api/) должен включать `inbox_image`, `inbox_date` или `inbox_days` — без одного из этих полей пуш доставляется как обычное уведомление и никогда не попадает в ленту входящих. Произвольные пользовательские данные помещаются под ключ `data`, который SDK доставляет клиенту в качестве параметра `u`:

```json title="POST https://api.pushwoosh.com/json/1.3/createMessage"
{
  "request": {
    "application": "XXXXX-XXXXX",
    "auth": "API_TOKEN",
    "notifications": [{
      "send_date": "now",
      "ios_title": "Summer sale",
      "content": "30% off everything — limited time only",
      "inbox_image": "https://cdn.example.com/inbox/summer.png",
      "inbox_days": 7,
      "data": {
        "displayType": "captioned",
        "promo_id": "SUMMER2026",
        "screen": "promo_details"
      },
      "platforms": [1]
    }]
  }
}
```

SDK предоставляет этот объект в сообщении входящих через `actionParams`. Считайте его из делегата, когда пользователь нажимает на строку или встроенную CTA:

```swift
extension MyInboxHost: PushwooshInboxKitDelegate {

    func inboxKit(_ vc: PushwooshInboxKitViewController,
                  didSelect message: PWInboxMessageProtocol) -> Bool {
        guard let params = message.actionParams as? [String: Any] else { return true }

        // Пользовательский объект `data` приходит под ключом "u" —
        // либо как вложенный словарь, либо как строка в формате JSON,
        // в зависимости от того, как была сформирована полезная нагрузка.
        let custom: [String: Any]? = {
            if let dict = params["u"] as? [String: Any] { return dict }
            if let raw = params["u"] as? String,
               let bytes = raw.data(using: .utf8),
               let parsed = try? JSONSerialization.jsonObject(with: bytes) as? [String: Any] {
                return parsed
            }
            return nil
        }()

        if let promoId = custom?["promo_id"] as? String {
            navigateToPromo(promoId)
            return false   // мы обработали нажатие; SDK не должен выполнять действие по умолчанию
        }
        return true
    }
}
```

Тот же поиск `actionParams["u"]` работает внутри `inboxKit(_:didTapButton:onMessage:)` для встроенных CTA-кнопок. Для типизированных случаев CTA (`openURL`, `dismiss`, `markRead`) SDK уже выполняет действие по умолчанию — верните `true`, чтобы сохранить это поведение, или `false`, чтобы подавить его и запустить собственную логику.

## Добавление встроенных CTA-кнопок

Сообщение может содержать до трёх встроенных кнопок призыва к действию (CTA). Кнопки находятся вместе с другими пользовательскими данными внутри `data` в виде массива `buttons`. SDK автоматически отображает их внутри ячеек с подписью и классических ячеек:

```json title="POST https://api.pushwoosh.com/json/1.3/createMessage"
{
  "request": {
    "application": "XXXXX-XXXXX",
    "auth": "API_TOKEN",
    "notifications": [{
      "send_date": "now",
      "ios_title": "New promo card",
      "content": "Tap a button to claim or save",
      "inbox_image": "https://cdn.example.com/inbox/promo.png",
      "inbox_days": 7,
      "data": {
        "displayType": "captioned",
        "promo_id": "SUMMER2026",
        "buttons": [
          { "title": "Claim", "url": "https://example.com/promo/SUMMER2026" },
          { "title": "Read",  "action": "markRead" },
          { "title": "Save",  "action": "custom", "tag": "save_promo" }
        ]
      },
      "platforms": [1]
    }]
  }
}
```

Каждый объект кнопки имеет следующие поля:

| Поле | Тип | Когда |
|---|---|---|
| `title` | string | Обязательно. Видимая метка кнопки. |
| `url` | string | Непустой URL, который можно разобрать, создаёт действие `openURL`. SDK открывает его через `UIApplication.shared.open`, если ваш делегат не подавляет это действие. |
| `action` | string | Явный токен действия: `dismiss` (удаляет сообщение из ленты), `markRead` (отмечает сообщение как прочитанное) или `custom` (обрабатывается хостом). Регистронезависимый. |
| Любое другое | any | Когда `action` равно `custom`, каждый ключ в объекте кнопки, кроме `title` и `action`, пересылается вашему делегату как пользовательская полезная нагрузка — согласуйте ключ с маркетологом (например, `tag`) и выполняйте диспетчеризацию по нему. |

Приоритет разрешения: сначала явный токен `action`, затем `url`, если он не пуст, в противном случае кнопка попадает в категорию `custom`, неся полную полезную нагрузку (за вычетом `title` и `action`).

Перехватывайте нажатия из вашего делегата. Свойство `button.action` является типизированным перечислением `PushwooshInboxButtonAction`:

```swift
extension MyInboxHost: PushwooshInboxKitDelegate {

    func inboxKit(_ vc: PushwooshInboxKitViewController,
                  didTapButton button: PushwooshInboxButton,
                  onMessage message: PWInboxMessageProtocol) -> Bool {
        switch button.action {
        case .openURL(let url):
            // Поведение по умолчанию подходит — пусть SDK откроет URL.
            return true

        case .dismiss, .markRead:
            // SDK обрабатывает оба случая. Верните false, если хотите переопределить.
            return true

        case .custom(let payload):
            // Пользовательская кнопка, определённая маркетологом. Выполняйте диспетчеризацию по согласованному ключу.
            if let tag = payload["tag"] as? String {
                switch tag {
                case "save_promo":
                    saveCurrentPromoLocally(message: message)
                default:
                    break
                }
            }
            return true   // игнорируется для custom — SDK никогда не выполняет здесь действие по умолчанию
        }
    }
}
```