Skip to content

iOS

Интеграция в приложения для iPhone и iPad. Общая схема — в разделе WebView: общая схема, здесь — готовый код на Swift.

Что понадобится

  • WKWebView с обработчиком сообщений AdEventsHandler.
  • Видеоплеер — в примере AVPlayer.
  • Адрес страницы SDK с платформой ios: https://cdn.adsdk.ru/ios/v3/.

Как устроен экран

Как приходят события

Каждое событие — один объект: name содержит имя события, остальные поля — данные.

json
{ "name": "AdLoaded", "ad_media_file_src_url": "https://cdn.example.com/ad.mp4", "ad_creative_type": "video/mp4", "ad_number": 1 }

Поля каждого события — в разделе События.

Полный пример

Создайте WKWebView и плеер

swift
import AVFoundation
import UIKit
import WebKit

final class AdViewController: UIViewController {

    private let player = AVPlayer()
    private lazy var playerLayer = AVPlayerLayer(player: player)
    private var webView: WKWebView!
    private var timeObserver: Any?
    private var statusObservation: NSKeyValueObservation?

    private let vastURL: String
    var onFinish: (() -> Void)?

    init(vastURL: String) {
        self.vastURL = vastURL
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .black
        view.layer.addSublayer(playerLayer)

        let config = WKWebViewConfiguration()
        config.allowsInlineMediaPlayback = true
        config.mediaTypesRequiringUserActionForPlayback = []
        // WKUserContentController держит обработчик сильно — передаём прокси со слабой ссылкой
        config.userContentController.add(WeakMessageHandler(self), name: "AdEventsHandler")

        webView = WKWebView(frame: view.bounds, configuration: config)
        webView.isOpaque = false
        webView.backgroundColor = .clear
        webView.scrollView.isScrollEnabled = false
        webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(webView)

        observePlayer()
        webView.load(URLRequest(url: adPageURL()))
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        playerLayer.frame = view.bounds
    }

    private func adPageURL() -> URL {
        var components = URLComponents(string: "https://cdn.adsdk.ru/ios/v3/")!
        components.queryItems = [
            URLQueryItem(name: "url", value: vastURL),
            URLQueryItem(name: "lang", value: "ru"),
            URLQueryItem(name: "controls", value: #"["ad-skip-btn","ad-click-btn","ad-more-menu"]"#),
        ]
        return components.url!
    }

    private func sdk(_ call: String) {
        webView.evaluateJavaScript("window.myAdController.\(call)")
    }
}

Получайте события

swift
extension AdViewController: WKScriptMessageHandler {

    func userContentController(_ controller: WKUserContentController, didReceive message: WKScriptMessage) {
        guard let body = message.body as? [String: Any], let name = body["name"] as? String else { return }

        switch name {
        case "AdLoaded":
            if body["ad_creative_type"] as? String == "application/javascript" {
                sdk("playAd()")                     // VPAID играет внутри WebView
            } else if let src = body["ad_media_file_src_url"] as? String, let url = URL(string: src) {
                player.replaceCurrentItem(with: AVPlayerItem(url: url))
                player.play()
            }
        case "AdSkipped":
            player.pause()
        case "AdClick":
            if let src = body["url"] as? String, let url = URL(string: src) {
                UIApplication.shared.open(url)
            }
        case "AdError":
            finish()
        case "AdDestroyed":
            player.pause()
            if body["ad_is_last"] as? Bool == true { finish() }
        default:
            break
        }
    }
}

/// Не даёт WKUserContentController удерживать контроллер после закрытия рекламы.
final class WeakMessageHandler: NSObject, WKScriptMessageHandler {
    private weak var target: WKScriptMessageHandler?

    init(_ target: WKScriptMessageHandler) { self.target = target }

    func userContentController(_ controller: WKUserContentController, didReceive message: WKScriptMessage) {
        target?.userContentController(controller, didReceive: message)
    }
}

Сообщайте SDK о воспроизведении

swift
private extension AdViewController {

    func observePlayer() {
        // Позиция каждые 250 мс → квартили и момент пропуска
        let interval = CMTime(seconds: 0.25, preferredTimescale: 600)
        timeObserver = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
            guard let self, let duration = self.player.currentItem?.duration.seconds,
                  duration.isFinite, duration > 0 else { return }
            self.sdk("timeupdateAd(\(time.seconds), \(duration))")
        }

        // Старт и пауза
        statusObservation = player.observe(\.timeControlStatus) { [weak self] player, _ in
            switch player.timeControlStatus {
            case .playing: self?.sdk("playAd()")
            case .paused where player.currentItem != nil: self?.sdk("pauseAd()")
            default: break
            }
        }

        // Конец ролика → AdComplete
        NotificationCenter.default.addObserver(
            forName: .AVPlayerItemDidPlayToEndTime, object: nil, queue: .main
        ) { [weak self] _ in
            guard let self, let duration = self.player.currentItem?.duration.seconds else { return }
            self.sdk("timeupdateAd(\(duration), \(duration))")
        }
    }
}

Завершите показ

swift
private extension AdViewController {

    func finish() {
        if let timeObserver { player.removeTimeObserver(timeObserver) }
        statusObservation = nil
        player.replaceCurrentItem(with: nil)
        webView.configuration.userContentController.removeScriptMessageHandler(forName: "AdEventsHandler")
        onFinish?()
    }
}

Частые ошибки

СимптомПричина
Белый экран вместо роликаНе выставлены isOpaque = false и backgroundColor = .clear.
Видео открывается на весь экранНет allowsInlineMediaPlayback = true.
VPAID не стартует без касанияНет mediaTypesRequiringUserActionForPlayback = [].
Контроллер не освобождаетсяОбработчик добавлен напрямую, без слабой ссылки.
Нет квартилейНе вызывается timeupdateAd().

Больше — в разделе Решение проблем.