# Add iAB Standard Size Experiences to an iOS app

Show tag-driven IAB banner, medium rectangle, half-page, interstitial, and collapsible ads in an iOS app with the iAB Standard Size Experience SDK 0.0.5.

The iAB Standard Size Experience SDK renders video and audio advertisements from Genuin's ad server inside your own app. You add one Swift package, initialize it with your brand's API key, and place a SwiftUI view that carries a **tag ID**. The tag identifies an ad placement configured in Brand Control Center, so campaigns, creatives, and targeting change without an app update.

The package `https://github.com/genuininc/ios_sdk` ships two products as binary xcframeworks:

| Product | Import | Adds | Binary size |
| --- | --- | --- | --- |
| GenuinSDK | import GenuinSDK | Genuin.shared, which initializes the platform and holds the API key, consent, and user session | 0.1 MB, plus Core 5.5 MB and _Core 0.1 MB |
| GenuinInStreamAds | import GenuinInStreamAds | The six ad views and the BGInStreamAds.module you register at initialization | 0.2 MB, plus _FeedCore 4.5 MB |

Sizes are the arm64 device binaries in the 0.0.5 package, before App Store compression. `Core`, `_Core`, and `_FeedCore` are shared frameworks that both products list as targets, so adding both products adds them once.

| Ad view | Size (points) | BGAdFormat case | Description |
| --- | --- | --- | --- |
| BG320x50AdView | 320 × 50 | .banner320x50 | Compact single-row banner |
| BG320x100AdView | 320 × 100 | .banner320x100 | Large banner |
| BG300x250AdView | 300 × 250 | .mediumRectangle300x250 | Medium rectangle |
| BGCollapsibleAdView | 300 × 250, collapsing to a single row | .collapsibleBanner | Medium rectangle that collapses after playback or when the user taps the chevron |
| BG320x480AdView | 320 × 480 | .portrait320x480 | Mobile interstitial |
| BG300x600AdView | 300 × 600 | .halfPage300x600 | Half page |

Every view fetches the creative for its tag, plays video and audio through Google Interactive Media Ads (IMA), tracks impressions, and handles taps, mute, and full-screen. You pick the view that matches the slot; there's no size argument.

## Requirements

| | Minimum |
| --- | --- |
| iOS | 16.0. Package.swift declares .iOS(.v16) and every framework's MinimumOSVersion is 16.0. |
| Xcode | 26 or later. Package.swift requires Swift tools 6.2; the 0.0.5 frameworks were built with Xcode 26.6 (Swift 6.3.3) with library evolution enabled. |
| Swift | Your target can use Swift 5 or Swift 6 language mode. The frameworks are built in Swift 5 mode with library evolution. |
| Devices | iPhone and iPad (UIDeviceFamily 1 and 2). |
| Architectures | arm64 devices; arm64 and x86_64 simulators. |
| Package manager | Swift Package Manager only. The package contains binary targets, so there's no CocoaPods spec. |
| UI toolkit | SwiftUI. UIKit apps host the views in a UIHostingController. |
| Platforms | iOS only. The package resolves but fails to build for macOS because Google IMA has no macOS build. |

You also need two values from your Genuin account. The **API key** comes from Brand Control Center under **Build** > **Publish Brand Community** > **SDK Integration**. The **tag ID** for each ad slot comes from your Genuin team. See [Step 5](#step-5-get-your-credentials).

## Step 1: Add the Genuin package

1. In Xcode, go to **File** > **Add Package Dependencies**.
2. Enter the repository URL `https://github.com/genuininc/ios_sdk`.
3. Set **Dependency Rule** to **Exact Version** and enter `0.0.5`, the latest release. **Up to Next Major Version** from `0.0.5` also accepts `0.x` patch releases.
4. Click **Add Package**.
5. In the product list, select both `GenuinSDK` and `GenuinInStreamAds`, assign them to your app target, and click **Add Package**.

Xcode downloads the five xcframeworks (`GenuinSDK`, `GenuinInStreamAds`, `Core`, `_Core`, `_FeedCore`) and links them as dynamic frameworks.

## Step 2: Add the Google IMA package

`GenuinInStreamAds` links `GoogleInteractiveMediaAds.framework`, but a binary Swift package can't declare that dependency for you. Add Google's package yourself, or the app crashes at launch:

```text title="Crash log"
dyld: Library not loaded: @rpath/GoogleInteractiveMediaAds.framework/GoogleInteractiveMediaAds
```

1. Go to **File** > **Add Package Dependencies** again.
2. Enter `https://github.com/googleads/swift-package-manager-google-interactive-media-ads-ios`.
3. Set **Dependency Rule** to **Branch** and enter `main`. This is the rule the package's `Package.swift` documents; the latest tagged IMA release is 3.33.0.
4. Add the `GoogleInteractiveMediaAds` product to your app target.

Only IMA is required. The iAB Standard Size Experience SDK doesn't use Google Mobile Ads, so don't add that package.

## Step 3: Verify the linked frameworks

Open your app target, go to **General** > **Frameworks, Libraries, and Embedded Content**, and confirm that `GenuinSDK`, `GenuinInStreamAds`, and `GoogleInteractiveMediaAds` are listed. Swift Package Manager embeds and signs them during the build, so no other embedding setting is needed.

## Step 4: Configure Info.plist and tracking

The SDK reads the App Tracking Transparency (ATT) status and the advertising identifier for ad measurement, but it never shows the ATT prompt. Add the usage description and request authorization yourself:

```xml title="Info.plist"
<key>NSUserTrackingUsageDescription</key>
<string>We use your data to show you more relevant ads.</string>
```

```swift title="Tracking prompt"
import AppTrackingTransparency

ATTrackingManager.requestTrackingAuthorization { _ in }
```

Request it after launch or at a suitable point in onboarding, before the first ad view appears. Without authorization the identifier is zeroed and ads still serve, but without personalization.

Ad assets load over HTTPS, so App Transport Security needs no changes. Don't enable `NSAllowsArbitraryLoads` for ad delivery.

## Step 5: Get your credentials

| Credential | Purpose | Where to get it |
| --- | --- | --- |
| API key | Authenticates your app. Passed once to initialize. | Brand Control Center > Build > Publish Brand Community > SDK Integration. The screen has a Live Environment and a QA Environment toggle; each environment has its own key. |
| Tag ID | Identifies one ad slot and its campaigns. Passed to each ad view. | The id of an iAB Standard Tag created in Brand Control Center under Grow > Off Site > Extended > iAB Standard Tag and accepted for your brand. Your Genuin team passes it to you. |

The API key is a client-side credential, but keep it out of source control. Put it in an `.xcconfig` file that your build injects into `Info.plist`, or in your CI secrets. Read it at runtime with `Bundle.main.object(forInfoDictionaryKey:)`.

## Step 6: Initialize the SDK

Call `Genuin.shared.initialize` once at launch and pass `BGInStreamAds.module`. Without the module the ad views have no rendering engine and every unit fails with `BGTagError.adsManagerUnavailable`.

```swift title="MyApp.swift"
import SwiftUI
import GenuinSDK
import GenuinInStreamAds

@main
struct MyApp: App {
    init() {
        Genuin.shared.initialize(
            modules: [BGInStreamAds.module],
            apiKey: "<<YOUR_API_KEY>>",
            config: BGSDKConfig(navigationStyle: .system, environment: .prod)   // optional; these are the defaults
        ) { result in
            if case .failure(let error) = result { print("Genuin init failed: \(error)") }
        }
    }

    var body: some Scene {
        WindowGroup { ContentView() }
    }
}
```

In a UIKit app, make the same call in `application(_:didFinishLaunchingWithOptions:)`.

### Initialization parameters

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| modules | [Any.Type] | Yes | — | The feature modules to register. Pass [BGInStreamAds.module] for ads. |
| apiKey | String | Yes | — | Your brand's API key for the chosen environment. |
| config | BGSDKConfig | No | .default | environment is .prod or .qa; navigationStyle is .system, .custom, or .hidden and controls the navigation bar of screens the SDK presents. |
| completion | ((Result<Void, Error>) -> Void)? | No | nil | Called once initialization succeeds or fails. |

> **Note** A QA key only works with environment: .qa. Don't ship a build that points at QA; the two environments have separate brands, tags, and campaigns.

## Step 7: Display an ad

Each ad view is a SwiftUI `View`. Wrap it in `BGNavigationRoot`, which supplies the navigation container the SDK uses for full-screen playback and landing pages, and pass the tag ID:

```swift title="AdView.swift"
import SwiftUI
import GenuinInStreamAds
import Core

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Ad unit").font(.caption).foregroundStyle(.secondary)
            BGNavigationRoot {
                BG320x50AdView(tagID: "{{TAG_ID}}")
            }
        }
        .padding()
    }
}
```

The view fetches the creatives for the tag as soon as it appears. Every view fixes its own height and expands to the width of its container. Place it where the layout provides at least the slot's width: 320 points for the banners and interstitial, 300 points for the medium rectangle and half page. You don't set a frame.

All six views share one initializer:

```swift title="Initializer"
BG320x50AdView(tagID: String, delegate: (any BGAdDelegate)? = nil)
BG320x100AdView(tagID: "{{TAG_ID}}")
BG300x250AdView(tagID: "{{TAG_ID}}")
BGCollapsibleAdView(tagID: "{{TAG_ID}}")
BG320x480AdView(tagID: "{{TAG_ID}}")
BG300x600AdView(tagID: "{{TAG_ID}}")
```

`BGCollapsibleAdView` collapses to a single-row banner either on its own after playback (`BGCollapseReason.auto`) or when the user taps the chevron (`.user`), and reports both through the delegate.

### Host an ad view in UIKit

Wrap the SwiftUI view in a `UIHostingController` and add it as a child:

```swift title="UIKit host"
import UIKit
import SwiftUI
import GenuinInStreamAds
import Core

let hosting = UIHostingController(rootView: BGNavigationRoot { BG300x250AdView(tagID: "{{TAG_ID}}") })
addChild(hosting)
hosting.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(hosting.view)
NSLayoutConstraint.activate([
    hosting.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
    hosting.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
    hosting.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
])
hosting.didMove(toParent: self)
```

Initialize the SDK in `AppDelegate` before the first hosting controller is created.

## Step 8: Observe lifecycle events

Pass an object that conforms to `BGAdDelegate` to react to loading, playback, interaction, and layout events. Every method has an empty default implementation, so implement only the ones you need. `BGAdDelegate` and its payload types live in `_FeedCore`, so import it next to `GenuinInStreamAds`:

```swift title="AdTracker.swift"
import GenuinInStreamAds
import _FeedCore

final class AdTracker: BGAdDelegate {
    func iabAdUnitDidLoad(_ unit: BGAdUnitInfo, mediaCount: Int) {
        print("Loaded \(mediaCount) item(s) for tag \(unit.tagID ?? "-")")
    }
    func iabAdUnitDidReceiveNoAds(_ unit: BGAdUnitInfo) { /* hide or resize the slot */ }
    func iabAdUnitDidFailToLoad(_ unit: BGAdUnitInfo, error: any Error) {
        if let tagError = error as? BGTagError { print("Ad failed: \(tagError)") }
    }
    func iabAdMediaDidStart(_ mediaItem: BGMediaInfo) { /* impression started */ }
    func iabAdAllMediaDidComplete(_ unit: BGAdUnitInfo) { /* the unit played through once */ }
    func iabAdCTATapped(_ mediaItem: BGMediaInfo, url: URL) { print("CTA tapped: \(url)") }
}
```

Attach it to a view and keep a strong reference, because the protocol is class-bound and the view doesn't own the delegate:

```swift title="ContentView.swift"
struct ContentView: View {
    @State private var tracker = AdTracker()

    var body: some View {
        BGNavigationRoot {
            BG300x250AdView(tagID: "{{TAG_ID}}", delegate: tracker)
        }
    }
}
```

### Delegate methods

| Method | Payload | Fires when |
| --- | --- | --- |
| iabAdUnitDidStartLoading(_:) | BGAdUnitInfo | The unit started fetching its feed |
| iabAdUnitDidLoad(_:mediaCount:) | BGAdUnitInfo, Int | The feed resolved to one or more renderable items |
| iabAdUnitDidReceiveNoAds(_:) | BGAdUnitInfo | The feed resolved but was empty |
| iabAdUnitDidFailToLoad(_:error:) | BGAdUnitInfo, any Error | The fetch failed; the error is a BGTagError for tag problems |
| iabAdMediaDidAppear(_:) | BGMediaInfo | An item became the active on-screen cell |
| iabAdMediaDidStart(_:) | BGMediaInfo | The creative resolved and playback began |
| iabAdMediaDidFailToFill(_:) | BGMediaInfo | An audio item's ad-slot waterfall produced no fill |
| iabAdMediaDidComplete(_:) | BGMediaInfo | The item's audio or video reached the end |
| iabAdMediaDidChange(from:to:) | BGMediaInfo?, BGMediaInfo | The pager moved between items |
| iabAdAllMediaDidComplete(_:) | BGAdUnitInfo | The last item finished |
| iabAdMuteStateDidChange(_:isMuted:) | BGMediaInfo, Bool | The user muted or unmuted |
| iabAdWatchTapped(_:) | BGMediaInfo | The user tapped Watch |
| iabAdCTATapped(_:url:) | BGMediaInfo, URL | The user tapped the call to action or the advertiser domain |
| iabAdDidEnterFullscreen(_:), iabAdDidExitFullscreen(_:) | BGMediaInfo | Full-screen playback opened or closed |
| iabAdDidCollapse(_:reason:), iabAdDidExpand(_:) | BGMediaInfo, BGCollapseReason | A collapsible unit collapsed (.auto or .user) or expanded |

### Payload types

| Type | Fields |
| --- | --- |
| BGAdUnitInfo | unitID: String, tagID: String?, format: BGAdFormat |
| BGMediaInfo | unit: BGAdUnitInfo, index: Int, mediaCount: Int, mediaID: String, kind: BGAdKind (.sponsored or .audioAd), creative: BGAudioCreative?, mediaItem: BGFeedModel? |
| BGAudioCreative | audioURL, videoURL?, backgroundColorHex, brandLogoURL?, brandName, advertiserDomain, durationSeconds, transcript, vastXML |
| BGTagError | .invalidTagID when the tag ID is empty or malformed; .adsManagerUnavailable when BGInStreamAds.module wasn't passed to initialize |

When a unit fails or has no ads, the view keeps its size and shows a placeholder so your layout doesn't shift. To reclaim the space, hide the slot from `iabAdUnitDidReceiveNoAds` or `iabAdUnitDidFailToLoad`.

## Troubleshooting

| Symptom | Cause | Fix |
| --- | --- | --- |
| The app crashes at launch with dyld: Library not loaded: @rpath/GoogleInteractiveMediaAds.framework | The Google IMA package isn't in the project or isn't linked to the app target | Complete Step 2. |
| Every slot shows the placeholder and iabAdUnitDidFailToLoad reports .adsManagerUnavailable | initialize was called without BGInStreamAds.module, or after the first ad view appeared | Pass the module and initialize in App.init or didFinishLaunchingWithOptions. |
| iabAdUnitDidFailToLoad reports .invalidTagID | The tag ID is empty or malformed | Pass the tag ID exactly as your Genuin team provided it. |
| iabAdUnitDidReceiveNoAds fires for a valid tag | The tag has no active campaign, or the key belongs to the other environment | Check the tag in Brand Control Center and match environment to the key. |
| Build error: no such module GenuinSDK or GenuinInStreamAds | The products are resolved at the project level but not added to the app target, or the deployment target is below iOS 16 | Add both products to the target and set the deployment target to 16.0 or later. |
| Build error: no such module _FeedCore or Core | The file imports a type from a shared framework without importing it | Add import _FeedCore for BGAdDelegate and payload types, import Core for BGNavigationRoot and BGSDKConfig. |
| Resolution or build error mentioning macOS | The target supports macOS or Mac Catalyst | Remove macOS from the target's supported destinations. |
| The ad plays but isn't personalized | ATT authorization wasn't requested or was denied | Request authorization before the first ad view, as in Step 4. |

## Limitations

- **iOS 16 and later only**: The package declares `.iOS(.v16)` and the frameworks require it.
- **SwiftUI first**: The ad views are SwiftUI views; UIKit apps need a `UIHostingController` for each slot.
- **IMA is your dependency**: Binary targets can't declare it, so every app adds the Google IMA package itself, on branch `main`.
- **No macOS or Mac Catalyst**: Google IMA has no macOS build, so the package is iOS only.
- **Six fixed sizes**: There's no flexible or landscape view in 0.0.5; the Android iAB Standard Size Experience SDK's `BG1280x720AdView` and `BGFSLandscapeAdView` have no iOS counterpart yet.
- **Tag-only delivery**: There's no direct ad-URL parameter; every creative resolves from a tag ID managed in Brand Control Center.
- **Binary size**: The five frameworks add about 10.4 MB of arm64 code before App Store compression, plus Google IMA.
- **Pre-1.0 versioning**: Releases are `0.0.x` tags; pin an exact version because `0.x` minor releases can change the API.

## See also

- [Add iAB Standard Size Experiences to an Android app](https://begenuin.com/developers/developer-resources/sdk/integrate-the-genuin-ad-sdk-for-android)
- [Integrate the Genuin iOS SDK](https://begenuin.com/developers/developer-resources/sdk/developer/genuin-sdk/native-compatibility/ios/how-to-integrate-and-power-your-brand-community-in-ios-apps)
- [Integrate the Genuin Android SDK](https://begenuin.com/developers/developer-resources/sdk/how-to-integrate-and-power-your-brand-community-in-android-apps)
- [How the Genuin SDK places ads in a feed](https://begenuin.com/developers/browse-by-products/monetize/how-does-the-genuin-sdk-render-ads-and-deliver-the-consumer-experience)
