Skip to main content
Version: 4.2.0

Integration Guide

Complete guide to integrating the BidMachine Plus iOS SDK. Code samples are provided in Swift and Objective-C — use the language switch on each block.

BidMachine Plus iOS SDK

BidMachine Plus exposes two demand paths through one API:

ModeDescription
AdNetworkBidMachine runs as a header-bidding demand source — on its own, or plugged into a third-party mediation
MediationBidMachine acts as the mediation platform itself, running the auction across your demand

The same ad unit classes work in both modes, selected at initialization:

SwiftObjective-C
BannerAdBMBannerAd
InterstitialAdBMInterstitialAd
RewardedAdBMRewardedAd

This page covers SDK installation, initialization, and ad loading. Two installation paths are available: automated via AI coding agents, or manual setup with CocoaPods or Swift Package Manager.

Automated Integration with AI Coding Agents

The BidMachine Plus SDK Agents bundle ships a portable integration skill for Claude Code, Codex, and Gemini. Install it for your runtime:

/plugin marketplace add bidmachine/bidmachine-sdk-agents
/plugin install bidmachine-sdk-agents@bidmachine

Then ask your agent to integrate BidMachine Plus — for example:

Prompt
Integrate BidMachine Plus into my iOS app with interstitial, rewarded, and banner ads.

The bundled skill drives the integration: it adds the SDK dependency, initializes in AdNetwork or Mediation mode, wires ad units, and sets privacy flags (GDPR, CCPA, ATT), preserving any existing ad setup.

Manual Installation

Requires iOS 13.0+ and Xcode 16.4+. Import the module as import BidMachinePlus (Swift) or @import BidMachinePlus; (Objective-C).

CocoaPods

The Static subspec is the default and injects the -ObjC linker flag automatically.

Podfile
platform :ios, '13.0'
use_frameworks!

target 'YourApp' do
pod 'BidMachinePlus' # default (Static)
# pod 'BidMachinePlus/Static'
# pod 'BidMachinePlus/Dynamic'
end

Swift Package Manager

  1. File → Add Package Dependencies
  2. URL: https://github.com/bidmachine/BidMachinePlus-SPM
  3. Pick version 0.1.0 or a range, and add the BidMachinePlus product
  4. Add -ObjC to Build Settings → Other Linker Flags
Package.swift
.package(url: "https://github.com/bidmachine/BidMachinePlus-SPM", from: "0.1.0")
// product: .product(name: "BidMachinePlus", package: "BidMachinePlus-SPM")

Info.plist Configuration

Allow arbitrary loads so ad creatives can render:

Info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>

Insert the SKAdNetworkItems array — the full list of network IDs is published with the SDK.

If you collect IDFA, add an ATT usage description:

Info.plist
<key>NSUserTrackingUsageDescription</key>
<string>$(APP_NAME) needs your advertising identifier to deliver personalized ads.</string>

Initialization

Call BidMachine.instance(appKey:) once at app startup to get the SDK instance, then call initialize(config:completion:). Keep a reference to the instance — you'll pass it to every ad unit constructor. The same app key always returns the same instance. The completion fires on the main queue.

BidMachine Plus initializes in one of two modes, selected by the integrationType you pass to the builder.

AdNetwork Mode

BidMachine runs as a header-bidding ad network. When it plugs into a third-party mediation, name that platform with the mediator setter so it can compete in the waterfall.

let sdk = BidMachine.instance(appKey: "YOUR_APP_KEY")

let config = InitializationConfigBuilder(integrationType: .adNetwork)
.with(mediator: MediatorName.admob) // or MediatorName.levelPlay, MediatorName.max, a custom String
.with(loggingEnabled: true) // remove before release
.with(testModeEnabled: true) // remove before release
.build()

sdk.initialize(config: config) { status, error in
if let error { print("init failed: \(error.message)") }
}

MediatorName.admob / BMMediatorNameAdMob ("admob"), .levelPlay / BMMediatorNameLevelPlay ("level_play"), and .max / BMMediatorNameMax ("max") are predefined string constants; the mediator setter also accepts any custom string.

Mediation Mode

BidMachine acts as the mediation platform itself, running the auction across your demand. Pass the mediation type — there's no third-party mediator to declare:

let config = InitializationConfigBuilder(integrationType: .mediation)
.with(loggingEnabled: true) // remove before release
.with(testModeEnabled: true) // remove before release
.build()

BidMachine.instance(appKey: "YOUR_APP_KEY").initialize(config: config) { status, error in }

In both modes the ad unit classes expose notifyWin / notifyLoss for reporting the outcome of a waterfall round — see Mediation.

Loading against a server-side bid? See S2S Bidding for load(bidPayload:).

Ad Integration

All ad lifecycle callbacks deliver an AdInfo / BMAdInfo describing the winning ad:

PropertyType (Swift / Obj-C)Description
placementIdString / NSString *Placement ID from the BidMachine dashboard
priceDouble / doubleeCPM ÷ 1000 (e.g. 0.005 = $5 CPM)
precisionRevenuePrecisionOne of .estimated, .exact, .unknown
info[String: String] / NSDictionaryNetwork metadata. Known keys: networkName, dsp, ecpm

Read the winning network via adInfo.info["networkName"], and the per-impression revenue via adInfo.price.

adInfo.precision describes how reliable that price is:

SwiftObj-CMeaning
.exactRevenuePrecisionExactReal-time auction price — trust for reporting
.estimatedRevenuePrecisionEstimatedHistorical-data estimate — treat as approximate
.unknownRevenuePrecisionUnknownConfidence cannot be determined

All delegate callbacks fire on the main thread, so you can update UI from them directly. On every delegate, only didLoadAd and didFailToLoadAd are required — the rest are optional.

Interstitial Ads

Full-screen ads. Create once, load, then show when ready. Reload from didDismiss to keep an ad ready for the next show.

MainViewController.swift
class MainViewController: UIViewController, InterstitialDelegate {
private var interstitialAd: InterstitialAd!

private func createInterstitialAd() {
interstitialAd = InterstitialAd(sdk, placementId: "YOUR_PLACEMENT_ID")
interstitialAd.delegate = self
interstitialAd.load()
}

private func showInterstitialAd() {
if interstitialAd.isLoaded {
interstitialAd.show(from: self)
} else {
print("Interstitial ad not ready yet")
}
}

// InterstitialDelegate
func didLoadAd(adInfo: AdInfo) {
print("Interstitial loaded from \(adInfo.info["networkName"] ?? "?")")
}
func didFailToLoadAd(adInfo: AdInfo?, error: BidMachineError) {
print("Interstitial load failed: \(error.message)")
}
func didShowAd(adInfo: AdInfo) {}
func didFailToShowAd(adInfo: AdInfo?, error: BidMachineError) {
print("Interstitial show failed: \(error.message)")
}
func didClick(adInfo: AdInfo) {}
func didDismiss(adInfo: AdInfo) {
interstitialAd.load() // reload for next show
}
func didExpire(adInfo: AdInfo) {}
func didPayRevenue(adInfo: AdInfo) {
print("Interstitial revenue: \(adInfo.price) from \(adInfo.info["networkName"] ?? "?")")
}
}
CallbackDescription
didLoadAdAd loaded and ready to show
didFailToLoadAdAd failed to load
didShowAdAd is displayed full-screen
didFailToShowAdAd failed to display
didClickUser tapped the ad
didDismissUser dismissed the ad
didExpireAd expired before being shown
didPayRevenueBillable impression recorded
note

InterstitialAd and RewardedAd have no destroy() method — to release a fullscreen ad, drop your reference to it. (BannerAd does expose destroy().)

Rewarded Ads

Same lifecycle as interstitial, plus a didReceiveReward callback when the user earns a reward. RewardedDelegate inherits from InterstitialDelegate.

MainViewController.swift
class MainViewController: UIViewController, RewardedDelegate {
private var rewardedAd: RewardedAd!

private func createRewardedAd() {
rewardedAd = RewardedAd(sdk, placementId: "YOUR_PLACEMENT_ID")
rewardedAd.delegate = self
rewardedAd.load()
}

private func showRewardedAd() {
if rewardedAd.isLoaded {
rewardedAd.show(from: self)
}
}

// RewardedDelegate
func didLoadAd(adInfo: AdInfo) {}
func didReceiveReward(adInfo: AdInfo, reward: Reward?) {
print("Reward earned: \(reward?.amount ?? 0) \(reward?.label ?? "")")
// grant reward to the user
}
func didFailToLoadAd(adInfo: AdInfo?, error: BidMachineError) {
print("Rewarded load failed: \(error.message)")
}
func didDismiss(adInfo: AdInfo) {
rewardedAd.load() // reload for next show
}
func didPayRevenue(adInfo: AdInfo) {
print("Rewarded revenue: \(adInfo.price) from \(adInfo.info["networkName"] ?? "?")")
}
}

didReceiveReward adds to the interstitial callback set. The Reward / BMReward object exposes label: String and amount: Int.

BannerAd / BMBannerAd is a UIView. Add it to your own layout, or call show(at:) to anchor it to a screen edge. It is single-shot — it does not auto-refresh.

Size (Swift)Obj-CDimensionsUse case
.bannerBMBannerAdSize.banner320 × 50Standard banner
.leaderboardBMBannerAdSize.leaderboard728 × 90Tablets
.mrecBMBannerAdSize.mrec300 × 250Medium rectangle
.adaptive(width:maxHeight:)+adaptiveWithWidth:maxHeight:customFluid layout
tip

Use BannerAdSize.getMaxAdaptiveHeight(width:) (+getMaxAdaptiveHeightWithWidth: in Obj-C) to calculate maxHeight for adaptive banners. width and maxHeight are UInt32.

Manual Banner

Reserve a host UIView, then attach the loaded banner to it inside didLoadAd:

MainViewController.swift
class MainViewController: UIViewController, BannerDelegate {
@IBOutlet private var adContainer: UIView!
private var bannerAd: BannerAd!

private func createBannerAd() {
bannerAd = BannerAd(sdk, placementId: "YOUR_PLACEMENT_ID", size: .banner)
bannerAd.delegate = self
bannerAd.load()
}

deinit { bannerAd?.destroy() }

// BannerDelegate
func didLoadAd(adInfo: AdInfo) {
adContainer.addSubview(bannerAd)
}
func didFailToLoadAd(adInfo: AdInfo?, error: BidMachineError) {
print("Banner load failed: \(error.message)")
}
func didShowAd(adInfo: AdInfo) {}
func didFailToShowAd(adInfo: AdInfo?, error: BidMachineError) {}
func didClick(adInfo: AdInfo) {}
func didExpire(adInfo: AdInfo) {}
func didPayRevenue(adInfo: AdInfo) {
print("Banner revenue: \(adInfo.price) from \(adInfo.info["networkName"] ?? "?")")
}
}
CallbackDescription
didLoadAdAd loaded and ready to attach to a superview
didFailToLoadAdAd failed to load
didShowAdAd visible on screen, impression tracked
didFailToShowAdAd failed to display
didClickUser tapped the ad
didExpireAd expired before being shown
didPayRevenueBillable impression recorded

Positioned Banner

Call show(at:) on a loaded banner to anchor it to one of the edges below. The SDK resolves the host view internally (the key window's rootViewController, with a top-presented-VC fallback) — no host view is required at the call site. Call hide() to remove it.

PositionSwiftObj-C
Top.horizontalTopBMBannerPositionHorizontalTop
Bottom.horizontalBottomBMBannerPositionHorizontalBottom
Left.verticalLeftBMBannerPositionVerticalLeft
Right.verticalRightBMBannerPositionVerticalRight
// In didLoadAd:
bannerAd.show(at: .horizontalBottom) // position defaults to .horizontalBottom

// Later:
bannerAd.hide() // detaches the view; show(at:) can re-display it
bannerAd.destroy()

.verticalLeft / .verticalRight rotate the banner 90° into a side strip (non-adaptive sizes only — adaptive banners fall back to a horizontal-bottom layout). hide() only detaches the view; it does not call destroy().

Publisher Extras

Attach arbitrary key → value pairs forwarded with the bid request. The API has the same shape at two levels:

ScopeWhere to callApplies to
SDK-widesdk.addExtraEvery auction from this instance
Per ad unit<AdUnit>.addExtraOnly that placement's auctions

Passing nil as the value removes the key. At load time per-ad-unit extras are merged on top of the SDK-wide values — per-ad keys win on collision.

SDK-wide

sdk.addExtra("whale", forKey: "user_segment")
let all: [String: String] = sdk.extras
sdk.addExtra(nil, forKey: "user_segment") // remove

Per ad unit

Available on BannerAd, InterstitialAd, and RewardedAd, with the same addExtra(_:forKey:) signature as the SDK-wide call:

interstitialAd.addExtra("level_complete", forKey: "placement_context")
let adExtras: [String: String] = interstitialAd.extras