Нативная реклама (SwiftUI)
Нативная реклама — реклама, внешний вид которой может определяться на стороне приложения. Данная особенность позволяет изменять визуальный стиль объявлений и места их размещения с учетом особенностей дизайна приложения.
Нативные объявления улучшают впечатления от рекламы, поэтому вы можете показывать больше объявлений, не теряя интерес пользователей. Это позволяет обеспечить максимальный доход от рекламы в долгосрочной перспективе.
Отрисовка рекламы производится нативными средствами платформы, что увеличивает ее производительность и качество.
Внешний вид
Это руководство покажет, как интегрировать нативную рекламу в iOS-приложение на SwiftUI. В дополнение к примерам кода и инструкции оно содержит рекомендации и ссылки на дополнительные ресурсы.
Пререквизит
- Выполните шаги по интеграции SDK, описанные в Быстром старте.
- Заранее проинициализируйте рекламный SDK.
- Убедитесь, что используете последнюю версию Yandex Mobile Ads SDK, а в случае использования медиации — актуальную версию единой сборки.
Имплементация
Основные шаги по интеграции нативной рекламы в SwiftUI:
- Создать
NativeAdState(request:options:)с запросомAdRequestи идентификатором рекламного места. Состояние должно жить дольше одного рендера: держите его в@StateObjectили во вью-модели. - Добавить в иерархию
ViewконтейнерNativeAdContainer(state:content:placeholder:)с этим состоянием. - Сверстать карточку внутри контейнера из примитивов
NativeAdAsset— это единственный способ показать компоненты объявления. - Подписаться на события через модификаторы
.onAdLoad,.onAdFailure,.onAdBindingFailure,.onAdClick,.onAdImpression. - Передать дополнительные настройки, если вы работаете через систему Adfox (через параметры
AdRequest). - Запустить загрузку вызовом
loadAd(), например вonAppear.
Особенности подключения нативной рекламы
-
Все вызовы методов Yandex Mobile Ads SDK необходимо выполнять из главного потока.
NativeAdStateиNativeAdContainerизолированы на@MainActor. -
Если вы получили ошибку в
.onAdFailure, не пытайтесь загрузить новое объявление снова. Если это необходимо сделать, ограничьте число повторных попыток загрузки рекламы, чтобы избежать неудачных запросов и проблем с подключением. -
Одно состояние обслуживает один контейнер. Если второй
NativeAdContainerполучит тот жеNativeAdState, привязка перейдет к нему: показы и события остаются только у контейнера, который привязался последним, а первый перестает их получать; в лог уходит предупреждение. -
NativeAdStateдолжен создаваться один раз. Держите его в@StateObjectили во вью-модели, а не создавайте вbody: контейнер, получивший новое состояние, отвязывает предыдущее объявление и начинает с фазы.idle. В ленте@StateObjectво вью элемента тоже подходит:LazyVStackиListсохраняют его при уходе элемента с экрана и возврате, реклама не загружается повторно. Если загрузку нужно начинать заранее, до появления элемента на экране, храните состояния во вью-модели. -
.onAdLoadи.onAdFailure— события контейнера: они доставляются, только пока контейнер находится в иерархии, и не являются признаком того, что реклама показана. Если результат загрузки нужен независимо от видимости карточки, подпишитесь во вью-модели наstate.$phase, а на контейнере оставьте.onAdClick,.onAdImpressionи.onAdBindingFailure. -
Компоненты объявления показываются только через примитивы
NativeAdAsset. Обязательный компонент, для которого примитив не отрисован или скрыт через.hidden()либо.drawingGroup(), блокирует привязку всего объявления: примерно через две секунды приходит.onAdBindingFailureс кодомNativeErrorCode.noViewForAsset, а имя компонента лежит вuserInfo["asset_name"]. Компонент с.opacity(0)привязку не ломает, но такой показ помечается как показ с невидимым обязательным компонентом. Поэтому лишний компонент не прячьте, а не рисуйте:if layout.has(.rating)вместо.hidden()и.opacity(0). -
Рекомендуется использовать макет, который включает весь набор возможных компонентов. Как показывает практика, макеты, включающие весь набор компонентов, приводят к лучшим конверсиям.
-
Объявления с видео, как правило, имеют более высокий CTR и, соответственно, приносят больше дохода. Для отображения рекламы с видео необходимо, чтобы размер рекламного контейнера и компонента
NativeAdAsset.Mediaбыли не меньше 300x160 dp (density-independent pixels). -
В
Info.plistобязателен ключSKAdNetworkItems, иначе загрузка рекламы завершится ошибкой. Подробнее — в разделе SKAdNetwork.
Загрузка рекламы
Загрузкой владеет NativeAdState. Текущее состояние загрузки публикуется в свойстве phase:
.idle— загрузка еще не начиналась;.loading— загрузка идет;.loaded(NativeAd)— реклама загружена и может быть показана;.failed(Error)— загрузка завершилась ошибкой.
Загрузка начинается только по явному вызову loadAd(); после ошибки повторный loadAd() отправляет запрос еще раз. Чтобы загрузить объявление заново с новыми параметрами, создайте новый NativeAdState с нужным AdRequest. Это аналог повторного вызова loadAd(with:) у NativeAdLoader в UIKit.
Параметры запроса за рекламой настраиваются через объект класса AdRequest. В качестве параметров запроса нужно передать идентификатор рекламного блока, также дополнительно можно настроить таргетинг и другие данные, способные улучшить качество подбора рекламы. Параметры загрузки изображений передаются через NativeAdOptions. Подробнее читайте в разделе Таргетирование рекламы.
import SwiftUI
import YandexMobileAds
struct NativeAdView: View {
@StateObject private var adState = NativeAdState(request: AdRequest(adUnitID: "R-M-XXXXX-YY"))
var body: some View {
NativeAdContainer(state: adState) { layout in
adCard(layout)
} placeholder: {
ProgressView()
}
.onAdLoad { _ in
// Объявление успешно загружено
}
.onAdFailure { error in
// Ошибка загрузки
}
.onAppear { adState.loadAd() }
}
}
Идентификатор рекламного места (adUnitId) получается в интерфейсе Рекламной сети Яндекса.
Показ рекламы и верстка карточки
NativeAdContainer(state:content:placeholder:) показывает placeholder в фазах .idle и .loading, карточку — в фазе .loaded и ничего — после ошибки. В замыкание content приходит NativeAdLayoutInfo, который описывает, что есть в объявлении, но не отдает сами значения:
has(_:)— есть ли в объявлении компонент указанного вида;media?.aspectRatio— соотношение сторон медиа;media?.hasVideo— содержит ли медиа видео;adType— тип объявления;warningMinimumArea— минимальная доля площади карточки, которую должно занимать предупреждение.
Компоненты объявления рисуются примитивами NativeAdAsset: Title, Body, CallToAction, Domain, Sponsored, Age, Price, ReviewCount, Warning, Icon, Favicon, MainImage, Rating, Feedback и Media. Каждый примитив читает значение из объявления, отрисовывает его через замыкание label и регистрирует получившуюся вью в SDK — так работают валидация показа и обработка клика.
У текстовых примитивов замыкание принимает String, у Icon, Favicon, MainImage и Feedback — Image, у Rating — Double. Feedback показывает иконку по правилу UIKit-кнопки: загруженное изображение из ответа, иначе — иконку SDK. Для всех примитивов, кроме Rating, есть инициализатор без аргументов: текст рисуется как Text, изображение — как Image с resizable(), поэтому задайте ему размер через frame.
Полный перечень компонентов объявления, обязательных и опциональных, приведен в разделе Компоненты нативной рекламы.
Совет
Рекомендуется использовать макет, который включает весь набор возможных компонентов. Как показывает практика, использование такого макета приводит к более высоким конверсиям.
Пример карточки:
private func adCard(_ layout: NativeAdLayoutInfo) -> some View {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 8) {
NativeAdAsset.Icon()
.frame(width: 40, height: 40)
.clipShape(RoundedRectangle(cornerRadius: 8))
VStack(alignment: .leading) {
NativeAdAsset.Title().font(.headline).lineLimit(2)
HStack(spacing: 4) {
NativeAdAsset.Domain().font(.caption).foregroundColor(.secondary)
NativeAdAsset.Sponsored().font(.caption2).foregroundColor(.secondary)
NativeAdAsset.Age().font(.caption2).foregroundColor(.secondary)
NativeAdAsset.Favicon().frame(width: 14, height: 14)
}
}
Spacer()
NativeAdAsset.Feedback()
.frame(width: 24, height: 24)
}
NativeAdAsset.Media()
.clipShape(RoundedRectangle(cornerRadius: 12))
NativeAdAsset.Body().font(.subheadline).lineLimit(3)
if layout.has(.rating) || layout.has(.reviewCount) || layout.has(.price) {
HStack(spacing: 8) {
NativeAdAsset.Rating { value in
Text(String(format: "%.1f", value)).font(.caption)
}
NativeAdAsset.ReviewCount().font(.caption).foregroundColor(.secondary)
Spacer()
NativeAdAsset.Price { text in
Text(text).font(.caption).fontWeight(.semibold)
}
}
}
NativeAdAsset.CallToAction { text in
Text(text)
.bold()
.frame(maxWidth: .infinity, minHeight: 44)
.background(Color.accentColor)
.foregroundColor(.white)
.clipShape(Capsule())
}
NativeAdAsset.Warning().font(.caption2).foregroundColor(.secondary)
}
.padding(12)
}
Видео в объявлении
NativeAdAsset.Media сохраняет соотношение сторон медиа и показывает видео со стандартными контролами SDK. Если нужны свои контролы, используйте вариант с замыканием overlay — в него приходит NativeAdVideoState со свойствами position, duration, isMuted, isMuteControlHidden и методом setMuted(_:).
NativeAdAsset.Media { video in
VStack {
Spacer()
HStack {
Button(video.isMuted ? "Включить звук" : "Выключить звук") {
video.setMuted(!video.isMuted)
}
.opacity(video.isMuteControlHidden ? 0 : 1)
Spacer()
Text("\(Int(video.position)) / \(Int(video.duration))")
}
}
.padding(8)
}
События
События доставляются модификаторами контейнера:
|
Модификатор |
Значение в замыкании |
Когда вызывается |
|
|
|
Реклама успешно загружена |
|
|
|
Загрузка завершилась ошибкой |
|
|
|
Загруженную рекламу не удалось связать с отрисованной карточкой |
|
|
— |
Пользователь нажал на объявление |
|
|
|
Показ засчитан |
NativeAdContainer(state: adState) { layout in
adCard(layout)
} placeholder: {
ProgressView()
}
.onAdLoad { adInfo in
// ...
}
.onAdFailure { error in
// ...
}
.onAdBindingFailure { error in
// ...
}
.onAdClick {
// ...
}
.onAdImpression { impressionData in
// ...
}
Ручная загрузка изображений
Если изображения нужно загружать самостоятельно, передайте в состояние NativeAdOptions(shouldLoadImagesAutomatically: false) и вызовите loadImages(). Метод асинхронный: он завершается, когда изображения загружены, и примитивы перерисовываются сами — как завершение loadImages(completionHandler:) в UIKit.
@StateObject private var adState = NativeAdState(
request: AdRequest(adUnitID: "R-M-XXXXX-YY"),
options: NativeAdOptions(shouldLoadImagesAutomatically: false)
)
func loadImages() {
Task { await adState.loadImages() }
}
Важно
Показ засчитывается по видимости карточки и не ждет изображений. До завершения loadImages() примитивы Icon, Favicon, MainImage и Media рисуют пустое место, поэтому показывайте свой плейсхолдер.
Реклама в ленте
В ленте на каждое объявление заводится свой NativeAdState. В примере ниже состояния хранятся во вью-модели: так загрузку можно начать заранее, не дожидаясь появления элемента на экране. Элементы ForEach различаются по state.id. Поддерживаются и LazyVStack, и List: когда элемент возвращается на экран, SDK заново привязывает рекламу, при этом показ повторно не засчитывается.
@MainActor
final class FeedViewModel: ObservableObject {
@Published private(set) var adStates: [NativeAdState] = []
func addAd() {
let state = NativeAdState(request: AdRequest(adUnitID: "R-M-XXXXX-YY"))
adStates.append(state)
state.loadAd()
}
}
struct FeedView: View {
@StateObject private var viewModel = FeedViewModel()
var body: some View {
ScrollView {
LazyVStack(spacing: 16) {
ForEach(viewModel.adStates) { state in
NativeAdContainer(state: state) { layout in
adCard(layout)
} placeholder: {
ProgressView().frame(maxWidth: .infinity, minHeight: 80)
}
.onAdClick { }
.onAdImpression { _ in }
.onAdBindingFailure { _ in }
}
}
}
}
}
Загрузка нескольких рекламных объявлений
Yandex Mobile Ads SDK предоставляет возможность загрузки нескольких рекламных объявлений одним запросом (до девяти объявлений).
Примечание
Используйте демоблок demo-native-bulk-yandex для AdUnitID. Посмотреть поддерживаемые платформы можно на странице Демоблоки для тестирования.
-
Создайте экземпляр класса
NativeBulkAdLoader. -
Создайте
AdRequestс идентификатором рекламного блока иNativeAdOptionsдля дополнительных параметров. -
Вызовите метод
loadAds(with:adsCount:options:)для загрузки рекламы. -
Оберните каждое полученное объявление в
NativeAdState(ad:)и покажите его отдельнымNativeAdContainer.
@MainActor
final class BulkViewModel: ObservableObject {
@Published private(set) var adStates: [NativeAdState] = []
private let adLoader = NativeBulkAdLoader()
func loadAds() async {
let request = AdRequest(adUnitID: "demo-native-bulk-yandex")
let options = NativeAdOptions()
do {
let ads = try await adLoader.loadAds(with: request, adsCount: 3, options: options)
adStates = ads.map { NativeAdState(ad: $0) }
} catch {
// Ошибка загрузки
}
}
}
Примечание
Реклама, переданная в NativeAdState(ad:), считается уже загруженной, поэтому onAdLoad для такого состояния не вызывается.
Массив рекламных объявлений, полученный в результате балкового запроса, может содержать от 0 до adsCount объектов NativeAd. Все полученные объекты рекламы можно показывать независимо друг от друга.
Тестирование интеграции нативной рекламы
Использование демоблоков для тестирования рекламы
Для проверки корректной интеграции нативной рекламы, а также для тестирования вашего приложения, рекомендуется использовать тестовую рекламу.
Для гарантированного возврата тестовых объявлений на каждый запрос за рекламой, мы создали специальный демонстрационный идентификатор рекламного места. Используйте его для проверки корректной интеграции рекламы.
Демонстрационный adUnitId для комбинаторной рекламы: demo-native-content-yandex.
Демонстрационный adUnitId для рекламы мобильных приложений: demo-native-app-yandex.
Важно
Убедитесь, что перед выкладыванием приложения в store, вы заменили демонстрационный идентификатор рекламного места на настоящий, полученный в интерфейсе Рекламной сети Яндекса.
Проверка корректной интеграции рекламы
Проверить корректность интеграции рекламы можно через нативный инструмент Console.
Чтобы получить возможность просматривать расширенные логи, необходимо вызвать метод enableLogging класса YandexAds.
YandexAds.enableLogging()
Для просмотра логов SDK в инструменте Console установите Subsystem = com.mobile.ads.ads.sdk. Вы можете фильтровать логи по категории и уровню ошибки.
В случае обнаружения проблем при интеграции рекламы вы увидите подробный отчет о проблемах и рекомендации по их устранению.
Дополнительные ресурсы
- Ссылка на github.