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:

ProductImportAddsBinary size
GenuinSDKimport GenuinSDKGenuin.shared, which initializes the platform and holds the API key, consent, and user session0.1 MB, plus Core 5.5 MB and _Core 0.1 MB
GenuinInStreamAdsimport GenuinInStreamAdsThe six ad views and the BGInStreamAds.module you register at initialization0.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 viewSize (points)BGAdFormat caseDescription
BG320x50AdView320 × 50.banner320x50Compact single-row banner
BG320x100AdView320 × 100.banner320x100Large banner
BG300x250AdView300 × 250.mediumRectangle300x250Medium rectangle
BGCollapsibleAdView300 × 250, collapsing to a single row.collapsibleBannerMedium rectangle that collapses after playback or when the user taps the chevron
BG320x480AdView320 × 480.portrait320x480Mobile interstitial
BG300x600AdView300 × 600.halfPage300x600Half 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
iOS16.0. Package.swift declares .iOS(.v16) and every framework's MinimumOSVersion is 16.0.
Xcode26 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.
SwiftYour target can use Swift 5 or Swift 6 language mode. The frameworks are built in Swift 5 mode with library evolution.
DevicesiPhone and iPad (UIDeviceFamily 1 and 2).
Architecturesarm64 devices; arm64 and x86_64 simulators.
Package managerSwift Package Manager only. The package contains binary targets, so there's no CocoaPods spec.
UI toolkitSwiftUI. UIKit apps host the views in a UIHostingController.
PlatformsiOS 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 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:

Crash log
text
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:

Info.plist
xml
<key>NSUserTrackingUsageDescription</key>
<string>We use your data to show you more relevant ads.</string>
Tracking prompt
swift
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

CredentialPurposeWhere to get it
API keyAuthenticates 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 IDIdentifies 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.

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

ParameterTypeRequiredDefaultDescription
modules[Any.Type]Yes—The feature modules to register. Pass [BGInStreamAds.module] for ads.
apiKeyStringYes—Your brand's API key for the chosen environment.
configBGSDKConfigNo.defaultenvironment 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)?NonilCalled 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:

AdView.swift
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:

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

UIKit host
swift
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:

AdTracker.swift
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:

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

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

Delegate methods

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

Payload types

TypeFields
BGAdUnitInfounitID: String, tagID: String?, format: BGAdFormat
BGMediaInfounit: BGAdUnitInfo, index: Int, mediaCount: Int, mediaID: String, kind: BGAdKind (.sponsored or .audioAd), creative: BGAudioCreative?, mediaItem: BGFeedModel?
BGAudioCreativeaudioURL, 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

SymptomCauseFix
The app crashes at launch with dyld: Library not loaded: @rpath/GoogleInteractiveMediaAds.frameworkThe Google IMA package isn't in the project or isn't linked to the app targetComplete Step 2.
Every slot shows the placeholder and iabAdUnitDidFailToLoad reports .adsManagerUnavailableinitialize was called without BGInStreamAds.module, or after the first ad view appearedPass the module and initialize in App.init or didFinishLaunchingWithOptions.
iabAdUnitDidFailToLoad reports .invalidTagIDThe tag ID is empty or malformedPass the tag ID exactly as your Genuin team provided it.
iabAdUnitDidReceiveNoAds fires for a valid tagThe tag has no active campaign, or the key belongs to the other environmentCheck the tag in Brand Control Center and match environment to the key.
Build error: no such module GenuinSDK or GenuinInStreamAdsThe products are resolved at the project level but not added to the app target, or the deployment target is below iOS 16Add both products to the target and set the deployment target to 16.0 or later.
Build error: no such module _FeedCore or CoreThe file imports a type from a shared framework without importing itAdd import _FeedCore for BGAdDelegate and payload types, import Core for BGNavigationRoot and BGSDKConfig.
Resolution or build error mentioning macOSThe target supports macOS or Mac CatalystRemove macOS from the target's supported destinations.
The ad plays but isn't personalizedATT authorization wasn't requested or was deniedRequest 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

TRUST AND COMPLIANCE

© 2026 Genuin Inc.

Genuin Footer