View as Markdown

iOS

The Ezoic Ads SDK for iOS lets you display banner, rewarded, interstitial, native, outstream video, and instream video ads in native iOS apps. The SDK initializes Ezoic configuration, Prebid header bidding, Google Ad Manager, and consent handling from one integration point.

Requirements

  • iOS 14.0 or higher
  • Xcode 15.0 or higher
  • Swift 5.9 or higher
  • Google Mobile Ads application ID

Installation

Add the SDK with Swift Package Manager.

In Xcode, go to File > Add Package Dependencies and enter:

https://github.com/ezoic/ezoic-swift-sdk-dist.git

Select Exact Version and choose 1.5.0 (or the release version provided by Ezoic). Pinning an exact version is recommended so SDK updates are always intentional.

Or add the package to Package.swift using the release version provided for your app:

dependencies: [
    .package(url: "https://github.com/ezoic/ezoic-swift-sdk-dist.git", exact: "1.5.0")
]

The SDK is distributed as a pre-built EzoicAdsSDK.xcframework attached to each GitHub release. When you add the package, Swift Package Manager also resolves the Prebid Mobile and Google Mobile Ads SDKs as transitive dependencies — you do not need to add them yourself.

Initialize the SDK

Initialize the SDK in your AppDelegate or app startup flow before loading ads.

import EzoicAdsSDK

let config = EzoicConfiguration(
    domain: "example.com",
    debugEnabled: false,
    testMode: false
)

EzoicAds.shared.initialize(with: config) { result in
    switch result {
    case .success:
        print("Ezoic SDK initialized")
    case .failure(let error):
        print("Ezoic initialization failed: \(error)")
    }
}

domain must match the domain configured for your site in Ezoic. Authentication is handled by the app's bundle identifier and the configured domain — there is no client-side API key.

For apps using async/await on iOS 15 or higher:

try await EzoicAds.shared.initialize(with: config)

Add a Banner Ad

Create an EzoicBannerView, add it to your view hierarchy, and call loadAd() after the SDK is initialized.

import EzoicAdsSDK
import UIKit

class ViewController: UIViewController {
    private var bannerView: EzoicBannerView?

    override func viewDidLoad() {
        super.viewDidLoad()

        let adView = EzoicBannerView(adUnitIdentifier: 12345)
        adView.delegate = self
        adView.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(adView)

        NSLayoutConstraint.activate([
            adView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            adView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            adView.widthAnchor.constraint(equalToConstant: EzoicBannerSize.banner.width),
            adView.heightAnchor.constraint(equalToConstant: EzoicBannerSize.banner.height)
        ])

        bannerView = adView
        adView.loadAd()
    }
}

extension ViewController: EzoicBannerViewDelegate {
    func bannerViewDidLoad(_ bannerView: EzoicBannerView) {
        print("Banner loaded")
    }

    func bannerView(_ bannerView: EzoicBannerView, didFailToLoadWithError error: EzoicError) {
        print("Banner failed: \(error)")
    }
}

Replace 12345 with your numeric Ezoic ad unit identifier. The SDK fetches the Google Ad Manager ad unit, Prebid configuration, supported sizes, targeting values, and refresh interval from Ezoic servers.

You can load ads with an adaptive default size, a typed size, one size string, or a list of size strings.

adView.loadAd()
adView.loadAd(size: EzoicBannerSize.mediumRectangle)
adView.loadAd(size: "300x250")
adView.loadAd(sizes: ["300x250", "320x50"])

Common sizes include:

  • EzoicBannerSize.banner: 320x50
  • EzoicBannerSize.largeBanner: 320x100
  • EzoicBannerSize.mediumRectangle: 300x250
  • EzoicBannerSize.fullBanner: 468x60
  • EzoicBannerSize.leaderboard: 728x90
  • EzoicBannerSize.custom(width:height:): custom dimensions

You can also use the adaptive size for the current screen width:

let adaptiveSize = EzoicBannerSize.adaptiveSize

Add a Rewarded Ad

Rewarded ads are full-screen ads that grant an in-app reward when the user finishes watching. They use a load-then-show lifecycle: load the ad ahead of time (for example, at the start of a level), then present it at a natural break and grant the reward when it is earned.

import EzoicAdsSDK

var rewardedAd: EzoicRewardedAd?

func loadRewardedAd() {
    EzoicRewardedAd.load(adUnitIdentifier: 12345) { [weak self] result in
        switch result {
        case .success(let ad):
            self?.rewardedAd = ad
            ad.delegate = self
        case .failure(let error):
            print("Rewarded load failed: \(error.localizedDescription)")
        }
    }
}

func showRewardedAd() {
    rewardedAd?.show(from: self) { reward in
        print("Earned \(reward.amount) \(reward.type)")
        grantReward(reward.amount)
    }
}

async/await is also supported on iOS 15 or higher:

rewardedAd = try await EzoicRewardedAd.load(adUnitIdentifier: 12345)

Replace 12345 with your numeric Ezoic ad unit identifier. Call show(from:) only after the ad has loaded; showing before the load completes (or after the ad was already shown) reports EzoicError.adNotReady. If you pass nil (or omit from:), the SDK presents from the app's top view controller. Rewarded ads are single-use — load a new ad for each opportunity.

Lifecycle events are delivered through EzoicRewardedAdDelegate. The reward type and amount come from the reward configured on the Google Ad Manager rewarded ad unit.

extension ViewController: EzoicRewardedAdDelegate {
    func rewardedAd(_ rewardedAd: EzoicRewardedAd, userDidEarn reward: EzoicReward) {
        grantReward(reward.amount)
    }

    func rewardedAdDidDismiss(_ rewardedAd: EzoicRewardedAd) {
        print("Rewarded ad closed")
    }
}

The delegate also reports rewardedAdDidPresent, rewardedAd(_:didFailToPresentWithError:), rewardedAdDidRecordImpression, and rewardedAdDidRecordClick. All delegate methods are optional.

Add an Interstitial Ad

Interstitial ads are full-screen ads shown at natural transition points (for example, between levels or screens). Unlike rewarded ads, they grant no reward. They use the same load-then-show lifecycle: load the ad ahead of time, then present it at a natural break.

import EzoicAdsSDK

var interstitialAd: EzoicInterstitialAd?

func loadInterstitialAd() {
    EzoicInterstitialAd.load(adUnitIdentifier: 12345) { [weak self] result in
        switch result {
        case .success(let ad):
            self?.interstitialAd = ad
            ad.delegate = self
        case .failure(let error):
            print("Interstitial load failed: \(error.localizedDescription)")
        }
    }
}

func showInterstitialAd() {
    interstitialAd?.show(from: self)
}

async/await is also supported on iOS 15 or higher:

interstitialAd = try await EzoicInterstitialAd.load(adUnitIdentifier: 12345)

Replace 12345 with your numeric Ezoic ad unit identifier. Call show(from:) only after the ad has loaded; showing before the load completes (or after the ad was already shown) reports EzoicError.adNotReady. If you pass nil (or omit from:), the SDK presents from the app's top view controller. Interstitial ads are single-use — load a new ad for each opportunity.

Lifecycle events are delivered through EzoicInterstitialAdDelegate.

extension ViewController: EzoicInterstitialAdDelegate {
    func interstitialAdDidDismiss(_ interstitialAd: EzoicInterstitialAd) {
        print("Interstitial ad closed")
    }
}

The delegate also reports interstitialAdDidPresent, interstitialAd(_:didFailToPresentWithError:), and interstitialAdDidRecordImpression/interstitialAdDidRecordClick. All delegate methods are optional.

Add an Outstream Video Ad

Outstream video ads render inline in your own view hierarchy, like a banner, but with a video creative in an SDK-built player. Create an EzoicOutstreamAdView, set its delegate, add it to your view hierarchy, and call loadAd().

import EzoicAdsSDK

var outstreamAd: EzoicOutstreamAdView?

func loadOutstreamAd() {
    let adView = EzoicOutstreamAdView(adUnitIdentifier: 12345)
    adView.delegate = self
    adContainer.addSubview(adView)
    outstreamAd = adView

    adView.loadAd()
}

Replace 12345 with your numeric Ezoic ad unit identifier. Set delegate before calling loadAd() so the load callback isn't missed. Unlike EzoicBannerView, loadAd() takes no size — the SDK renders at the size configured on the server (640×360 by default). Call destroy() when the hosting view is torn down (e.g. in deinit, or when a cell is recycled) so the underlying ad is released.

Lifecycle events are delivered through EzoicOutstreamAdViewDelegate.

extension ViewController: EzoicOutstreamAdViewDelegate {
    func outstreamViewDidLoad(_ outstreamView: EzoicOutstreamAdView) {
        print("Outstream ad loaded")
    }

    func outstreamView(_ outstreamView: EzoicOutstreamAdView, didFailToLoadWithError error: EzoicError) {
        print("Outstream failed: \(error.localizedDescription)")
    }
}

The delegate also reports outstreamViewDidRecordImpression, outstreamViewDidRecordClick, outstreamViewWillPresentScreen, and outstreamViewDidDismissScreen — all optional.

Add an Instream Video Ad

Instream video ads play inside your own video content. Your app owns the video player and the Google IMA SDK; the Ezoic SDK's job is to build the Google Ad Manager VAST ad-tag URL you hand to IMA. There is no Ezoic view — the deliverable is a URL string, so you retain the EzoicInstreamAd instance yourself. EzoicInstreamAd is a view-less, multi-use controller: unlike rewarded and interstitial ads it is not single-use, so keep the instance and call load(contentUrl:delegate:) again for the next video.

import EzoicAdsSDK
import GoogleInteractiveMediaAds

let instreamAd = EzoicInstreamAd(adUnitId: 12345)

func loadInstreamAd() {
    instreamAd.load(contentUrl: playingVideoUrl, delegate: self)
}

Tag delivery and failures arrive through EzoicInstreamAdDelegate:

extension ViewController: EzoicInstreamAdDelegate {
    func instreamAd(_ instreamAd: EzoicInstreamAd, didReceiveAdTag adTagUrl: String) {
        // Feed the tag to your own IMA ads request.
        let request = IMAAdsRequest(
            adTagUrl: adTagUrl,
            adDisplayContainer: adDisplayContainer,
            contentPlayhead: contentPlayhead,
            userContext: nil
        )
        adsLoader.requestAds(with: request)
    }

    func instreamAd(_ instreamAd: EzoicInstreamAd, didFailToLoadWithError error: EzoicError) {
        print("Instream load failed: \(error.localizedDescription)")
        // Play content without a preroll.
    }
}

Replace 12345 with your numeric Ezoic ad unit identifier. contentUrl (the URL of the video being played) is optional and, when supplied, is added to the ad tag for contextual targeting. On an IMA ad error, call getNextAdTagUrl() to retry the next floor in the waterfall — it returns nil once exhausted. On the IMA STARTED event, call reportImpression() (optionally passing a revenueUsd value) to record the Ezoic impression. Call destroy() when the player is torn down (e.g. in deinit) to cancel any in-flight request; the controller is otherwise reusable across loads.

Add a Native Ad

Native ads deliver ad assets (headline, image, body text, call to action) that you render inside your own view hierarchy instead of a fixed banner or full-screen format, so the ad matches the look and feel of your content.

import EzoicAdsSDK
import GoogleMobileAds

var nativeAd: EzoicNativeAd?

func loadNativeAd() {
    EzoicNativeAd.load(adUnitIdentifier: 12345) { [weak self] result in
        switch result {
        case .success(let ad):
            self?.nativeAd = ad
            ad.delegate = self
            guard let gmaAd = ad.nativeAd else { return }

            // Build a NativeAdView, populate its asset views, then attach the ad.
            let adView = NativeAdView()
            let headline = UILabel()
            headline.text = gmaAd.headline
            adView.headlineView = headline
            adView.addSubview(headline)
            adView.nativeAd = gmaAd

            self?.adContainer.addSubview(adView)
        case .failure(let error):
            print("Native load failed: \(error.localizedDescription)")
        }
    }
}

async/await is also supported on iOS 15 or higher:

nativeAd = try await EzoicNativeAd.load(adUnitIdentifier: 12345)

Replace 12345 with your numeric Ezoic ad unit identifier. The SDK returns the underlying Google Mobile Ads NativeAd on ad.nativeAd; populate a NativeAdView with your own styling and assign the ad to its nativeAd property.

Call destroy() when the hosting view is torn down (for example in deinit, or when a table or collection view cell is recycled) so the underlying ad is released.

Lifecycle events are delivered through EzoicNativeAdDelegate. All delegate methods are optional.

extension ViewController: EzoicNativeAdDelegate {
    func nativeAdDidRecordImpression(_ nativeAd: EzoicNativeAd) {
        print("Native ad impression")
    }

    func nativeAdDidRecordClick(_ nativeAd: EzoicNativeAd) {
        print("Native ad clicked")
    }
}

The delegate also reports nativeAdWillPresentScreen and nativeAdDidDismissScreen, which fire when a click opens and closes an overlay such as the ad's destination.

Required App Setup for Ads to Serve

A few configuration steps live in your app project and on your website, not in the SDK. Apple and Google require them, and they directly affect whether — and how well — ads fill. The SDK cannot add them for you (see What the SDK does vs. what you must add), so complete all of the following before testing fill.

1. Google Mobile Ads application ID

Add your Google Mobile Ads application ID to Info.plist. This ID is provided by Ezoic and can be found in your Ezoic dashboard. Use the value assigned to your app unless your Ezoic representative gives you a different one. Also set GADIsAdManagerApp so Google Mobile Ads runs in Ad Manager mode.

<key>GADApplicationIdentifier</key>
<string>ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX</string>
<key>GADIsAdManagerApp</key>
<true/>

Without a valid GADApplicationIdentifier, the Google Mobile Ads SDK will not initialize and no ads will serve.

2. App Tracking Transparency (ATT)

ATT has the single biggest impact on fill and CPM. Without it, the IDFA is unavailable (all zeros) and a large share of programmatic demand will not bid or will bid much lower.

Add the tracking usage description to Info.plist. Apple requires this string to live in the app's Info.plist — it cannot be supplied by the SDK because it is shown in the App Store privacy label and the system prompt.

<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized ads to you.</string>

The SDK presents the ATT prompt for you. During initialize, when the status is still undetermined, the SDK shows the prompt and waits for the user's decision before starting the ad stack, so the IDFA (if granted) is attached to the first ad request. You only need to supply the NSUserTrackingUsageDescription string above — no extra code.

// The SDK requests ATT (if undetermined) before loading ads.
EzoicAds.shared.initialize(with: config) { _ in
    // SDK ready — banners can load with the IDFA available.
}

If you prefer to drive the ATT flow yourself (for example, to show a pre-prompt or to control its timing), set requestATTBeforeAds: false on EzoicConfiguration and request authorization before initializing the SDK so the IDFA is available on the first ad request:

import AppTrackingTransparency

let config = EzoicConfiguration(domain: "example.com", requestATTBeforeAds: false)
ATTrackingManager.requestTrackingAuthorization { _ in
    EzoicAds.shared.initialize(with: config) { _ in }
}
If you disable the SDK's ATT handling, resolve the prompt before initializing the ad SDK. Initializing first means the first ad requests go out without the IDFA even when the user later grants permission, which lowers fill on the initial screen.

3. SKAdNetwork identifiers

SKAdNetworkItems lets buyers attribute installs when the IDFA is unavailable; missing identifiers suppress demand from buyers that require SKAdNetwork. Apple reads this key only from the app's main Info.plist — it is not aggregated from frameworks or SDKs, so it must be added to your app even though the SDK depends on Google Mobile Ads.

Add the SKAdNetworkItems array with Google's published identifiers (Google's own cstr6suwn9.skadnetwork plus the participating third-party buyers). Copy the current, complete list from Google's Prepare privacy strategies page — Google updates it as buyers are added.

<key>SKAdNetworkItems</key>
<array>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>cstr6suwn9.skadnetwork</string>
    </dict>
    <!-- … plus the remaining identifiers from Google's list … -->
</array>

Identifiers must be lowercase. If you add mediation partners, also include any identifiers those partners require.

4. app-ads.txt on your website

Host an app-ads.txt file at the root of the developer/website domain listed on your app's App Store page (for example, https://example.com/app-ads.txt). It authorizes the buyers and exchanges that may sell your inventory; a missing or incomplete file causes most programmatic demand to be filtered out. Ezoic provides the required entries — confirm the file is published and current.

What the SDK does vs. what you must add

Item Provided automatically by the SDK You must add
Google Mobile Ads + Prebid SDKs Yes (transitive dependencies)
Ad unit, sizes, targeting, Prebid config Yes (fetched from Ezoic)
GADApplicationIdentifier / GADIsAdManagerApp No (per-app value) Yes, in Info.plist
ATT prompt (request + wait before ads) Yes (SDK presents it during initialize)
NSUserTrackingUsageDescription string No (App Store privacy requirement) Yes, in Info.plist
SKAdNetworkItems No (Apple reads app Info.plist only) Yes, in Info.plist
app-ads.txt No (hosted on your domain) Yes, on your website

Apple and Google deliberately require these to live in the app bundle or on the publisher domain, so they cannot be bundled inside the SDK. Keep them in mind whenever you ship a new app.

The SDK can automatically read consent signals from UserDefaults when they are set by a consent management platform.

  • TCF v2 consent is read from IABTCF_* keys.
  • GPP consent is read from IABGPP_* keys.
  • US Privacy consent is read from the standard IAB privacy key.

You can also set consent manually:

EzoicAds.shared.setGDPRConsent(
    applies: true,
    consentString: "TCF_CONSENT_STRING"
)

EzoicAds.shared.setGPPConsent(
    gppString: "GPP_STRING",
    sectionIds: "7"
)

EzoicAds.shared.setSubjectToCOPPA(true)

Pageview Tracking

The SDK automatically tracks view controller navigation after initialization.

You can also track a pageview manually when a user navigates to a new screen or content view:

EzoicAds.shared.trackPageview { success in
    print("Pageview tracked: \(success)")
}

For apps using async/await on iOS 15 or higher:

let success = await EzoicAds.shared.trackPageview()

Troubleshooting

SDK Not Initializing

  1. Confirm the configured domain matches your Ezoic dashboard.
  2. Confirm the app has network access.
  3. Enable debugEnabled: true and review SDK logs in the Xcode console.

Ads Not Loading

  1. Initialize the SDK before calling loadAd().
  2. Confirm the Ezoic ad unit identifier is configured in Ezoic.
  3. Confirm the Google Mobile Ads application ID is present in Info.plist.
  4. Check consent configuration if traffic is subject to privacy regulations.