# iOS

Source: https://docs.ezoic.com/docs/mobileapps/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:

```text
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:

```swift
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.

```swift
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:

```swift
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.

```swift
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.

## Banner Sizes

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

```swift
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:

```swift
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.

```swift
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:

```swift
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.

```swift
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.

```swift
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:

```swift
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`.

```swift
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()`.

```swift
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`.

```swift
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](https://developers.google.com/interactive-media-ads/docs/sdks/ios); 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.

```swift
import EzoicAdsSDK
import GoogleInteractiveMediaAds

let instreamAd = EzoicInstreamAd(adUnitId: 12345)

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

Tag delivery and failures arrive through `EzoicInstreamAdDelegate`:

```swift
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.

```swift
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:

```swift
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.

```swift
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](#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.

```xml
<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.

```xml
<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.

```swift
// 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:

```swift
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](https://developers.google.com/ad-manager/mobile-ads-sdk/ios/privacy/strategies#enable-skadnetwork-to-track-conversions) page — Google updates it as buyers are added.

```xml
<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.

## Privacy and Consent

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:

```swift
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:

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

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

```swift
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.

