OMNIBOX iOS SDK Swift

Интеграция баннерной рекламы в iOS-приложения с помощью Prebid Mobile SDK 3.3.1 и SwiftUI.

Архитектура

┌─────────────────────────────────────────────────────┐
│  ContentView (SwiftUI)                              │
│    └─ IABoxBanner (UIViewRepresentable)             │
│         └─ IABoxBannerContainerView (UIView)        │
│              ├─ BannerView (Prebid) — bid request   │
│              └─ WKWebView — rendering HTML creative │
└─────────────────────────────────────────────────────┘
  1. PrebidMobile.BannerView отправляет OpenRTB bid-запрос на Prebid Server
  2. Сервер возвращает bid-ответ с HTML/JS креативом (adm)
  3. WKWebView рендерит полученный adm
Почему WKWebView вместо встроенного рендерера PrebidMobile?
Сервер OMNIBOX возвращает HTML/JS креатив со своим SDK (adboxsdk.new.js), содержащим трекинг событий на JavaScript, а не на уровне протокола OpenRTB. Встроенный рендерер PrebidMobile ожидает трекеры событий в формате OpenRTB (burl, eventtrackers), поэтому не может отрендерить такой креатив. WKWebView рендерит HTML как есть.

Требования

  • iOS 14.0+
  • Xcode 15.0+
  • Swift 5.9+

Установка

1. Добавление Prebid Mobile SDK

PrebidMobile — сторонний open-source пакет от Prebid.org, размещённый на GitHub.

В Xcode: File → Add Package Dependencies, введите URL:

https://github.com/prebid/prebid-mobile-ios

Выберите только:

  • PrebidMobile
Не добавляйте PrebidMobileAdMobAdapters, PrebidMobileGAMEventHandlers, PrebidMobileMAXAdapters — они не нужны для standalone-режима и подтягивают Google Mobile Ads SDK.

2. Настройка параметров

В IABoxBannerView.swift задайте параметры аккаунта:

let bannerConfigID = "test_banner"              // Config ID placement
let accountID = "com.iabox.ios-sdk-test-1"      // Account ID
let serverURL = "https://ia.box/ads/prebid"     // Prebid Server URL

3. App Tracking Transparency (IDFA)

Чтобы передавать реальный deviceId (IDFA) на рекламный сервер, необходимо получить разрешение пользователя через App Tracking Transparency.

Без разрешения сервер получает 00000000-0000-0000-0000-000000000000 вместо реального идентификатора.

ATT и инициализация SDK — независимые процессы. Вы можете инициализировать SDK в любое время.

Шаг 1. Добавление описания в Info.plist

В Build Settings проекта (или напрямую в Info.plist) добавьте ключ NSUserTrackingUsageDescription:

<key>NSUserTrackingUsageDescription</key>
<string>This identifier is used to deliver personalized ads.</string>

Или через Xcode Build Settings:

INFOPLIST_KEY_NSUserTrackingUsageDescription = "This identifier is used to deliver personalized ads."

Шаг 2. Запрос разрешения в коде

ATT-запрос должен вызываться после появления UI — иначе системный диалог не отобразится.

Вариант A — scenePhase (рекомендуется для SwiftUI)

Запрос при переходе сцены в .active. Гарантирует, что окно приложения уже отображено:

import AppTrackingTransparency
import AdSupport

@main
struct MyApp: App {
    @Environment(\.scenePhase) private var scenePhase
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .onChange(of: scenePhase) { phase in
            if phase == .active {
                if ATTrackingManager.trackingAuthorizationStatus == .notDetermined {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                        ATTrackingManager.requestTrackingAuthorization { _ in }
                    }
                }
            }
        }
    }
}
Важно: applicationDidBecomeActive не вызывается в SwiftUI-приложениях на основе сцен. Используйте scenePhase.
Вариант B — UIKit AppDelegate

Для UIKit-приложений (без scene lifecycle):

class AppDelegate: UIResponder, UIApplicationDelegate {
    func applicationDidBecomeActive(_ application: UIApplication) {
        if ATTrackingManager.trackingAuthorizationStatus == .notDetermined {
            ATTrackingManager.requestTrackingAuthorization { _ in }
        }
    }
}

Поведение статуса ATT

Статус ATTIDFAРезультат
.authorizedРеальный UUIDПерсонализированная реклама
.denied00000000-...Контекстная реклама
.restricted00000000-...Контекстная реклама
.notDetermined00000000-...Нужно запросить разрешение
Тестирование на симуляторе: чтобы снова показать ATT-диалог, удалите приложение с симулятора и запустите заново.

4. Сборка и запуск

Соберите и запустите проект: Cmd+R

Структура проекта

ios-sdk-test/
├── ios-sdk-test/
│   ├── ios_sdk_testApp.swift         # Entry point (SwiftUI App)
│   ├── ContentView.swift             # UI with banner load buttons
│   ├── IABoxBannerView.swift         # Prebid + WKWebView wrapper
│   └── Assets.xcassets/              # Resources
├── ios-sdk-test.xcodeproj/           # Xcode project
└── README.md

Как работает интеграция

1. Инициализация SDK

Prebid.shared.prebidServerAccountId = accountID
Prebid.shared.timeoutMillis = 10000
Prebid.shared.shareGeoLocation = true

try? Prebid.initializeSDK(serverURL: serverURL) { status, error in
    // Handle initialization result
}
initializeSDK проверяет {serverURL}/status. Если этот endpoint не настроен (404) — это не критично, bid-запросы всё равно будут отправляться на /openrtb2/auction.

2. Bid-запрос

let bannerView = BannerView(
    frame: CGRect(origin: .zero, size: adSize),
    configID: configID,
    adSize: adSize
)
bannerView.delegate = self
bannerView.loadAd()  // → POST {serverURL}/openrtb2/auction

3. Рендеринг креатива в WKWebView

Когда bid-ответ получен, adm (HTML-креатив) извлекается и загружается в WKWebView:

func bannerView(_ bannerView: BannerView, didReceiveAdWithAdSize adSize: CGSize) {
    if let adm = bannerView.lastBidResponse?.winningBid?.adm {
        renderAdm(adm)  // Loads HTML into WKWebView
    }
}

4. SwiftUI-обёртка

struct IABoxBanner: UIViewRepresentable {
    @ObservedObject var viewModel: BannerViewModel
    let configID: String
    let adSize: CGSize
    var onStatusUpdate: ((String) -> Void)?

    func makeUIView(context: Context) -> IABoxBannerContainerView { ... }
    func updateUIView(_ uiView: IABoxBannerContainerView, context: Context) { ... }
}

5. Обработка кликов

Клики по рекламе обрабатываются через WKNavigationDelegate и WKUIDelegate. Ссылки <a href> перехватываются в decidePolicyFor и открываются в Safari. window.open() из JS перехватывается в createWebViewWith и открывается в Safari.

Формат ответа сервера (adm)

Сервер OMNIBOX возвращает HTML/JS креатив. Трекинг событий (impression, click, viewability) выполняется внутри adboxsdk.new.js на стороне WebView.

<script src="https://dcdn.adbox.ru/adboxsdk.new.js" async></script>
<script>
    window.AdBox.push({
        "format": "simple",
        "srcType": "url",
        "src": "https://ia.box/ads/markup?bid=...&imp=...",
        "target": "#slot-...",
        "size": {"w": 300, "h": 250},
        "track": {
            "impression": ["https://ia.box/track/impression?..."],
            "click": ["https://ia.box/track/click?..."],
            "view": ["https://ia.box/track/viewable?..."],
            "load": ["https://ia.box/track/loaded?..."],
            "error": ["https://ia.box/track/error?..."]
        }
    })
</script>
<div id="slot-..."></div>

Устранение неполадок

No such module 'PrebidMobile'

Проверьте, что Swift Package добавлен: Project → Package Dependencies. Убедитесь, что добавлен только PrebidMobile, без дополнительных адаптеров.

SDK status check failed

Endpoint /status не настроен на сервере — это допустимо. Bid-запросы будут работать через /openrtb2/auction.

Creative model must be provided with event tracker

Лог от встроенного рендерера PrebidMobile — можно игнорировать. Креатив рендерится через WKWebView, минуя встроенный рендерер.

Google Mobile Ads SDK initialized without an application ID

Были добавлены лишние пакеты (PrebidMobileAdMobAdapters и т.д.). Удалите их, оставьте только PrebidMobile.

deviceId = 00000000-0000-0000-0000-000000000000

Пользователь не дал разрешение на трекинг (ATT). Проверьте NSUserTrackingUsageDescription в Info.plist. Убедитесь, что ATTrackingManager.requestTrackingAuthorization вызывается после появления UI. На симуляторе: удалите приложение и запустите заново.

Banner not displayed

Проверьте логи Xcode Console на сообщения loadBanner(), Bid received, Rendering adm. Убедитесь, что configID существует на сервере. Проверьте доступность сервера: curl https://ia.box/ads/prebid/openrtb2/auction