Appearance
Opera Ads iOS SDK - Client Bidding (C2S)
Which integration is this page for?
This page describes how to run your own auction by calling the Opera Ads SDK directly, so that you can compare an Opera Ads bid against other ad networks yourself.
If you integrate Opera Ads through a mediation platform (TopOn, TradPlus, AppLovin MAX, AdMob or LevelPlay), Opera Ads serves through that platform's waterfall and you do not need any of the APIs on this page. See the Mediation guides instead.
Overview
Client-Side Bidding (C2S), also known as In-App Bidding or Header Bidding, allows you to run real-time auctions directly within your app. This gives you full control over the bidding process and enables you to compare Opera Ads bids with other ad networks in real-time.
Benefits of Client Bidding
- Real-Time Competition: All ad networks bid simultaneously for each impression
- Higher Revenue: Increased competition often leads to higher eCPMs
- Full Control: You manage the auction logic and decide the winner
- Transparency: See exact bid prices from all networks
- Reduced Latency: No server-side auction delays
How It Works
┌─────────────────────────────────────────────────────────┐
│ 1. Request bids from multiple networks in parallel │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Opera Ads│ │ Network A│ │ Network B│ │
│ │ loadC2S │ │ load() │ │ load() │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ 2. Compare bid prices (eCPM) │
│ Opera: $1.50 │ Network A: $1.20 │ Network B: $1.80 │
│ │
│ 3. Notify auction results │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │notifyLose│ │notifyLose│ │notifyWin │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ 4. Show winning ad (Network B) │
└─────────────────────────────────────────────────────────┘Prerequisites
Before implementing client bidding, ensure you have:
Opera Ads SDK Integration
- SDK version 2.3.0 or higher
- Basic integration completed
- Working waterfall ads
Understanding of Auction Logic
- Familiarity with eCPM comparison
- Win/loss notification requirements
- Thread safety considerations
Multiple Ad Networks (optional but recommended)
- Other ad SDKs integrated (for comparison)
- Ability to run parallel ad loads
Basic Implementation
Step 1: Load Ads in Parallel
Load Opera Ads with .clientBidding auction type alongside other networks:
Swift Example:
swift
import OpAdxSdk
class BiddingManager {
private var operaAd: OpAdxInterstitialAd?
private var competitorAd: CompetitorInterstitialAd? // Your other network
func loadAdsForBidding() {
// Start both ad loads simultaneously
loadOperaAd()
loadCompetitorAd()
}
private func loadOperaAd() {
// Create Opera Ads instance with clientBidding auction type
operaAd = OpAdxInterstitialAd(
placementId: "s14198264979520",
auctionType: .clientBidding // ← Important: Use clientBidding
)
let listener = OpAdxInterstitialAdLoadListenerImp(
onAdLoaded: { [weak self] ad in
print("Opera ad loaded")
self?.checkAuctionComplete()
},
onAdFailedToLoad: { [weak self] error in
print("Opera ad failed: \(error.message)")
self?.checkAuctionComplete()
}
)
operaAd?.loadC2SBid(placementId: "s14198264979520", listener: listener)
}
private func loadCompetitorAd() {
// Load your other network's ad
competitorAd?.load { [weak self] success in
print("Competitor ad loaded: \(success)")
self?.checkAuctionComplete()
}
}
private func checkAuctionComplete() {
// Check if both ads finished loading
// Then run auction
}
}Objective-C Example:
objective-c
#import <OpAdxSdk/OpAdxSdk.h>
@interface BiddingManager ()
@property (nonatomic, strong) OpAdxInterstitialAdBridge *operaAd;
@property (nonatomic, strong) CompetitorInterstitialAd *competitorAd;
@end
@implementation BiddingManager
- (void)loadAdsForBidding {
// Start both ad loads simultaneously
[self loadOperaAd];
[self loadCompetitorAd];
}
- (void)loadOperaAd {
// Create Opera Ads instance with clientBidding auction type
self.operaAd = [[OpAdxInterstitialAdBridge alloc]
initWithPlacementId:@"s14198264979520"
auctionType:AdAuctionTypeClientBidding]; // ← Important
self.operaAd.delegate = self;
[self.operaAd loadC2SBid];
}
- (void)loadCompetitorAd {
// Load your other network's ad
[self.competitorAd loadWithCompletion:^(BOOL success) {
NSLog(@"Competitor ad loaded: %d", success);
[self checkAuctionComplete];
}];
}
@endStep 2: Compare Bid Prices
Get the eCPM from each ad and compare:
Swift:
swift
func runAuction() {
var bids: [(network: String, ecpm: Double, ad: Any)] = []
// Get Opera Ads bid
if let operaAd = operaAd,
let bid = operaAd.getBid() {
let ecpm = bid.getEcpm()
bids.append((network: "Opera", ecpm: ecpm, ad: operaAd))
print("Opera Ads bid: $\(ecpm)")
}
// Get competitor bid
if let competitorAd = competitorAd {
let ecpm = competitorAd.getEcpm() // Your network's method
bids.append((network: "Competitor", ecpm: ecpm, ad: competitorAd))
print("Competitor bid: $\(ecpm)")
}
// Sort by eCPM (highest first)
bids.sort { $0.ecpm > $1.ecpm }
// Determine winner
if let winner = bids.first {
print("Winner: \(winner.network) with $\(winner.ecpm)")
notifyAuctionResults(bids: bids)
}
}Objective-C:
objective-c
- (void)runAuction {
NSMutableArray<Bid *> *bids = [NSMutableArray array];
// Get Opera Ads bid
if (self.operaAd && self.operaAd.getBid) {
OpAdxAdBid *bid = [self.operaAd getBid];
double ecpm = [bid getEcpm];
[bids addObject:[[Bid alloc] initWithNetwork:@"Opera" ecpm:ecpm ad:self.operaAd]];
NSLog(@"Opera Ads bid: $%.4f", ecpm);
}
// Get competitor bid
if (self.competitorAd) {
double ecpm = [self.competitorAd getEcpm];
[bids addObject:[[Bid alloc] initWithNetwork:@"Competitor" ecpm:ecpm ad:self.competitorAd]];
NSLog(@"Competitor bid: $%.4f", ecpm);
}
// Sort by eCPM (highest first)
[bids sortUsingComparator:^NSComparisonResult(Bid *a, Bid *b) {
if (a.ecpm > b.ecpm) return NSOrderedAscending;
if (a.ecpm < b.ecpm) return NSOrderedDescending;
return NSOrderedSame;
}];
// Notify results
[self notifyAuctionResults:bids];
}Step 3: Notify Auction Results
Important: You MUST notify all participants of the auction result. Each notification can only be called once per ad.
Swift:
swift
func notifyAuctionResults(bids: [(network: String, ecpm: Double, ad: Any)]) {
guard let winner = bids.first else { return }
for (index, bid) in bids.enumerated() {
if index == 0 {
// Winner
if bid.network == "Opera", let operaAd = bid.ad as? OpAdxInterstitialAd {
// Notify Opera Ads it won
let secondPrice = bids.count > 1 ? bids[1].ecpm : 0.0
operaAd.getBid()?.notifyWin(
secondPrice: secondPrice,
bidderName: bids.count > 1 ? bids[1].network : ""
)
print("✅ Opera Ads won - showing ad")
showOperaAd(operaAd)
} else {
print("✅ Competitor won - showing their ad")
showCompetitorAd()
}
} else {
// Loser
if bid.network == "Opera", let operaAd = bid.ad as? OpAdxInterstitialAd {
// Notify Opera Ads it lost
operaAd.getBid()?.notifyLose(
lossReason: .lowerThanHighestPrice,
winnerPrice: winner.ecpm,
winnerBidder: winner.network
)
print("❌ Opera Ads lost to \(winner.network)")
}
}
}
}Objective-C:
objective-c
- (void)notifyAuctionResults:(NSArray<Bid *> *)bids {
if (bids.count == 0) return;
Bid *winner = bids.firstObject;
for (NSInteger i = 0; i < bids.count; i++) {
Bid *bid = bids[i];
if (i == 0) {
// Winner
if ([bid.network isEqualToString:@"Opera"]) {
OpAdxInterstitialAdBridge *operaAd = (OpAdxInterstitialAdBridge *)bid.ad;
OpAdxAdBid *adBid = [operaAd getBid];
double secondPrice = bids.count > 1 ? bids[1].ecpm : 0.0;
NSString *secondBidder = bids.count > 1 ? bids[1].network : @"";
[adBid notifyWinWithSecondPrice:secondPrice bidderName:secondBidder];
NSLog(@"✅ Opera Ads won - showing ad");
[self showOperaAd:operaAd];
} else {
NSLog(@"✅ Competitor won - showing their ad");
[self showCompetitorAd];
}
} else {
// Loser
if ([bid.network isEqualToString:@"Opera"]) {
OpAdxInterstitialAdBridge *operaAd = (OpAdxInterstitialAdBridge *)bid.ad;
OpAdxAdBid *adBid = [operaAd getBid];
[adBid notifyLoseWithLossReason:LossReasonLowerThanHighestPrice
winnerPrice:winner.ecpm
winnerBidder:winner.network];
NSLog(@"❌ Opera Ads lost to %@", winner.network);
}
}
}
}Step 4: Show Winning Ad
Swift:
swift
func showOperaAd(_ ad: OpAdxInterstitialAd) {
guard !ad.isAdInvalidated() else {
print("Opera ad expired")
return
}
let listener = OpAdxInterstitialAdInteractionListenerImp(
onAdClicked: {
print("Opera ad clicked")
},
onAdDisplayed: {
print("Opera ad displayed")
},
onAdDismissed: {
print("Opera ad dismissed")
},
onAdFailedToShow: { error in
print("Opera ad failed to show: \(error.message)")
}
)
ad.show(on: self, listener: listener)
}Ad Format Implementations
Banner Ads
Swift:
swift
import OpAdxSdk
class BannerBiddingManager {
private var operaBanner: OpAdxBannerAdView?
func loadBannerForBidding() {
operaBanner = OpAdxBannerAdView()
operaBanner?.setPlacementId("s14170965187264")
operaBanner?.setAdSize(.BANNER_MREC)
let listener = OpAdxBannerAdListenerImp(
onAdLoaded: { [weak self] bannerInfo in
print("Banner loaded for bidding")
self?.runBannerAuction()
},
onAdFailedToLoad: { error in
print("Banner failed: \(error.message)")
},
onAdImpression: {
print("Banner impression")
},
onAdClicked: {
print("Banner clicked")
}
)
// Load with client bidding mode
operaBanner?.loadC2SBidAd(listener: listener)
}
func runBannerAuction() {
guard let operaBanner = operaBanner,
let bid = operaBanner.getBid() else {
return
}
let operaEcpm = bid.getEcpm()
print("Opera Banner bid: $\(operaEcpm)")
// Compare with other networks and notify
if operaWon {
bid.notifyWin(secondPrice: secondPlaceEcpm, bidderName: "Competitor")
showBanner(operaBanner)
} else {
bid.notifyLose(lossReason: .lowerThanHighestPrice, winnerPrice: winnerEcpm, winnerBidder: "Winner")
}
}
func showBanner(_ banner: OpAdxBannerAdView) {
// Add banner to view hierarchy
view.addSubview(banner)
// ... setup constraints
}
}Objective-C:
objective-c
@interface BannerBiddingManager ()
@property (nonatomic, strong) OpAdxBannerAdBridge *operaBanner;
@end
@implementation BannerBiddingManager
- (void)loadBannerForBidding {
self.operaBanner = [[OpAdxBannerAdBridge alloc]
initWithPlacementId:@"s14170965187264"
adSize:OpAdxAdSize.BANNER_MREC];
self.operaBanner.delegate = self;
// Load with client bidding mode
[self.operaBanner loadC2SBidAd];
}
- (void)bannerAdDidLoad:(OpAdxBannerAdBridge *)bannerAd {
NSLog(@"Banner loaded for bidding");
[self runBannerAuction];
}
- (void)runBannerAuction {
OpAdxAdBid *bid = [self.operaBanner getBid];
if (!bid) return;
double operaEcpm = [bid getEcpm];
NSLog(@"Opera Banner bid: $%.4f", operaEcpm);
// Compare and notify
if (operaWon) {
[bid notifyWinWithSecondPrice:secondPlaceEcpm bidderName:@"Competitor"];
[self showBanner:self.operaBanner];
} else {
[bid notifyLoseWithLossReason:LossReasonLowerThanHighestPrice
winnerPrice:winnerEcpm
winnerBidder:@"Winner"];
}
}
@endNative Ads
Swift:
swift
import OpAdxSdk
class NativeBiddingManager {
private var operaNative: OpAdxNativeAd?
func loadNativeForBidding() {
operaNative = OpAdxNativeAd(
placementId: "s14198263063424",
auctionType: .clientBidding
)
let listener = OpAdxNativeAdListenerImp(
onAdLoaded: { [weak self] ad in
print("Native ad loaded for bidding")
self?.runNativeAuction()
},
onAdFailedToLoad: { error in
print("Native ad failed: \(error.message)")
},
onAdImpression: {
print("Native ad impression")
},
onAdClicked: {
print("Native ad clicked")
}
)
operaNative?.loadC2SBid(listener: listener)
}
func runNativeAuction() {
guard let operaNative = operaNative,
let bid = operaNative.getBid() else {
return
}
let operaEcpm = bid.getEcpm()
print("Opera Native bid: $\(operaEcpm)")
// Compare and notify
if operaWon {
bid.notifyWin(secondPrice: secondPlaceEcpm, bidderName: "Competitor")
renderNativeAd(operaNative)
} else {
bid.notifyLose(lossReason: .lowerThanHighestPrice, winnerPrice: winnerEcpm, winnerBidder: "Winner")
}
}
func renderNativeAd(_ ad: OpAdxNativeAd) {
let nativeAdView = OpAdxNativeAdView(frame: CGRect(x: 0, y: 0, width: 300, height: 400))
nativeAdView.configure(with: ad)
let rootView = OpAdxNativeAdRootView(root: nativeAdView)
ad.registerInteractionViews(
container: rootView,
interactionViews: nativeAdView.interactionViews,
adChoicePosition: .topRight
)
adContainer.addSubview(nativeAdView)
}
}Rewarded Ads
Swift:
swift
import OpAdxSdk
class RewardedBiddingManager {
private var operaRewarded: OpAdxRewardedAd?
func loadRewardedForBidding() {
operaRewarded = OpAdxRewardedAd(
placementId: "s14198592226752",
auctionType: .clientBidding
)
let listener = OpAdxRewardedAdLoadListenerImp(
onAdLoaded: { [weak self] ad in
print("Rewarded ad loaded for bidding")
self?.runRewardedAuction()
},
onAdFailedToLoad: { error in
print("Rewarded ad failed: \(error.message)")
}
)
operaRewarded?.loadC2SBid(placementId: "s14198592226752", listener: listener)
}
func runRewardedAuction() {
guard let operaRewarded = operaRewarded,
let bid = operaRewarded.getBid() else {
return
}
let operaEcpm = bid.getEcpm()
print("Opera Rewarded bid: $\(operaEcpm)")
// Compare and notify
if operaWon {
bid.notifyWin(secondPrice: secondPlaceEcpm, bidderName: "Competitor")
showRewardedAd(operaRewarded)
} else {
bid.notifyLose(lossReason: .lowerThanHighestPrice, winnerPrice: winnerEcpm, winnerBidder: "Winner")
}
}
}Advanced Topics
Loss Reasons
When notifying a loss, provide accurate loss reason:
Swift:
swift
public enum LossReason: Int {
case internalError = 1 // Internal SDK error
case lowerThanFloorPrice = 100 // Bid below floor price
case lowerThanHighestPrice = 102 // Lost to higher bid
}
// Usage
bid.notifyLose(
lossReason: .lowerThanHighestPrice,
winnerPrice: 1.50,
winnerBidder: "Network X"
)Objective-C:
objective-c
// Loss reason enum (bridged from Swift LossReason)
// LossReasonInternalError = 1
// LossReasonLowerThanFloorPrice = 100
// LossReasonLowerThanHighestPrice = 102
// Usage
[bid notifyLoseWithLossReason:LossReasonLowerThanHighestPrice
winnerPrice:1.50
winnerBidder:@"Network X"];Second Price Auction
In a second-price auction, the winner pays the second-highest bid price:
Swift:
swift
func runSecondPriceAuction(bids: [(name: String, ecpm: Double)]) {
let sorted = bids.sorted { $0.ecpm > $1.ecpm }
if let winner = sorted.first {
// Winner pays second price (or their bid if no second place)
let secondPrice = sorted.count > 1 ? sorted[1].ecpm : winner.ecpm
print("Winner: \(winner.name) at $\(winner.ecpm)")
print("Pays: $\(secondPrice) (second price)")
// Notify winner
if winner.name == "Opera" {
operaAd?.getBid()?.notifyWin(
secondPrice: secondPrice,
bidderName: sorted.count > 1 ? sorted[1].name : ""
)
}
}
}Floor Pricing
Reject bids below your floor price:
Swift:
swift
let floorPrice = 0.50 // $0.50 minimum eCPM
func runAuctionWithFloor(bids: [(name: String, ecpm: Double, ad: Any)]) {
var validBids = bids.filter { $0.ecpm >= floorPrice }
if validBids.isEmpty {
// No bids meet floor price
print("No bids above floor price of $\(floorPrice)")
// Notify all bids they lost due to floor
for bid in bids {
if bid.name == "Opera", let ad = bid.ad as? OpAdxInterstitialAd {
ad.getBid()?.notifyLose(
lossReason: .lowerThanFloorPrice,
winnerPrice: floorPrice,
winnerBidder: "Floor Price"
)
}
}
return
}
// Continue with auction using only valid bids
validBids.sort { $0.ecpm > $1.ecpm }
notifyResults(validBids)
}Timeout Handling
Handle cases where ads don't load in time:
Swift:
swift
class BiddingManager {
private var auctionTimer: Timer?
private let auctionTimeout: TimeInterval = 5.0 // 5 seconds
func startBidding() {
// Start parallel loads
loadOperaAd()
loadCompetitorAd()
// Set timeout
auctionTimer = Timer.scheduledTimer(
withTimeInterval: auctionTimeout,
repeats: false
) { [weak self] _ in
self?.handleAuctionTimeout()
}
}
func handleAuctionTimeout() {
print("Auction timeout - running with available bids")
runAuction()
}
func onAdLoaded() {
// Cancel timeout if all ads loaded
if allAdsLoaded {
auctionTimer?.invalidate()
runAuction()
}
}
}Thread Safety
Important: All AdBid methods must be called on the main thread:
Swift:
swift
func notifyWinner() {
DispatchQueue.main.async {
self.operaAd?.getBid()?.notifyWin(
secondPrice: secondPrice,
bidderName: "Competitor"
)
}
}Objective-C:
objective-c
- (void)notifyWinner {
dispatch_async(dispatch_get_main_queue(), ^{
OpAdxAdBid *bid = [self.operaAd getBid];
[bid notifyWinWithSecondPrice:secondPrice bidderName:@"Competitor"];
});
}Complete Example
Here's a complete implementation of client bidding for interstitial ads:
Swift:
swift
import OpAdxSdk
class CompleteBiddingExample: UIViewController {
private var operaAd: OpAdxInterstitialAd?
private var competitorAd: CompetitorInterstitial?
private var operaLoaded = false
private var competitorLoaded = false
// MARK: - Start Bidding
func startBidding() {
print("Starting client bidding auction...")
operaLoaded = false
competitorLoaded = false
loadOperaAd()
loadCompetitorAd()
}
// MARK: - Load Opera Ads
func loadOperaAd() {
operaAd = OpAdxInterstitialAd(
placementId: "s14198264979520",
auctionType: .clientBidding
)
let listener = OpAdxInterstitialAdLoadListenerImp(
onAdLoaded: { [weak self] ad in
print("✅ Opera ad loaded")
self?.operaLoaded = true
self?.checkBiddingComplete()
},
onAdFailedToLoad: { [weak self] error in
print("❌ Opera ad failed: \(error.message)")
self?.operaLoaded = true // Still mark as complete
self?.checkBiddingComplete()
}
)
operaAd?.loadC2SBid(placementId: "s14198264979520", listener: listener)
}
// MARK: - Load Competitor
func loadCompetitorAd() {
competitorAd = CompetitorInterstitial()
competitorAd?.load { [weak self] success in
if success {
print("✅ Competitor ad loaded")
} else {
print("❌ Competitor ad failed")
}
self?.competitorLoaded = true
self?.checkBiddingComplete()
}
}
// MARK: - Run Auction
func checkBiddingComplete() {
guard operaLoaded && competitorLoaded else { return }
print("Both ads finished loading - running auction")
runAuction()
}
func runAuction() {
var bids: [(name: String, ecpm: Double, ad: Any?)] = []
// Get Opera bid
if let operaAd = operaAd, let bid = operaAd.getBid() {
let ecpm = bid.getEcpm()
bids.append((name: "Opera", ecpm: ecpm, ad: operaAd))
print("Opera bid: $\(String(format: "%.4f", ecpm))")
}
// Get competitor bid
if let competitorAd = competitorAd {
let ecpm = competitorAd.getEcpm()
bids.append((name: "Competitor", ecpm: ecpm, ad: competitorAd))
print("Competitor bid: $\(String(format: "%.4f", ecpm))")
}
// Check if we have any valid bids
guard !bids.isEmpty else {
print("No valid bids - cannot show ad")
return
}
// Sort by eCPM (highest first)
bids.sort { $0.ecpm > $1.ecpm }
// Notify all participants
notifyAuctionResults(bids: bids)
}
func notifyAuctionResults(bids: [(name: String, ecpm: Double, ad: Any?)]) {
guard let winner = bids.first else { return }
print("🏆 Winner: \(winner.name) with $\(String(format: "%.4f", winner.ecpm))")
for (index, bid) in bids.enumerated() {
if index == 0 {
// Winner notification
if bid.name == "Opera", let operaAd = bid.ad as? OpAdxInterstitialAd {
let secondPrice = bids.count > 1 ? bids[1].ecpm : 0.0
let secondBidder = bids.count > 1 ? bids[1].name : ""
operaAd.getBid()?.notifyWin(
secondPrice: secondPrice,
bidderName: secondBidder
)
print("💰 Opera wins - pays $\(String(format: "%.4f", secondPrice))")
showOperaAd(operaAd)
} else {
print("💰 Competitor wins - showing their ad")
showCompetitorAd()
}
} else {
// Loser notification
if bid.name == "Opera", let operaAd = bid.ad as? OpAdxInterstitialAd {
operaAd.getBid()?.notifyLose(
lossReason: .lowerThanHighestPrice,
winnerPrice: winner.ecpm,
winnerBidder: winner.name
)
print("❌ Opera lost to \(winner.name)")
}
}
}
}
// MARK: - Show Ads
func showOperaAd(_ ad: OpAdxInterstitialAd) {
let listener = OpAdxInterstitialAdInteractionListenerImp(
onAdClicked: {
print("Opera ad clicked")
},
onAdDisplayed: {
print("Opera ad displayed")
},
onAdDismissed: { [weak self] in
print("Opera ad dismissed")
self?.operaAd = nil
},
onAdFailedToShow: { error in
print("Opera ad failed to show: \(error.message)")
}
)
ad.show(on: self, listener: listener)
}
func showCompetitorAd() {
competitorAd?.show(from: self)
}
}Best Practices
1. Always Notify Results
swift
// ✅ Good: Always notify win or lose
if won {
bid.notifyWin(secondPrice: secondPrice, bidderName: competitor)
} else {
bid.notifyLose(lossReason: .lowerThanHighestPrice, winnerPrice: winnerPrice, winnerBidder: winner)
}
// ❌ Bad: Forgetting to notify
if won {
bid.notifyWin(secondPrice: secondPrice, bidderName: competitor)
}
// Losers never notified!2. Call Notifications Only Once
swift
// ✅ Good: Single notification
bid.notifyWin(secondPrice: 1.20, bidderName: "Network A")
// ❌ Bad: Duplicate notifications (will be ignored)
bid.notifyWin(secondPrice: 1.20, bidderName: "Network A")
bid.notifyWin(secondPrice: 1.30, bidderName: "Network B") // Ignored!3. Use Main Thread
swift
// ✅ Good: Main thread
DispatchQueue.main.async {
bid.notifyWin(secondPrice: secondPrice, bidderName: competitor)
}
// ❌ Bad: Background thread (may cause issues)
DispatchQueue.global().async {
bid.notifyWin(secondPrice: secondPrice, bidderName: competitor)
}4. Handle Load Failures
swift
// ✅ Good: Handle failures
onAdFailedToLoad: { [weak self] error in
print("Opera ad failed: \(error.message)")
self?.operaLoaded = true // Mark as complete
self?.checkBiddingComplete() // Continue auction
}
// ❌ Bad: Ignore failures
onAdFailedToLoad: { error in
// Auction never completes if this ad fails!
}5. Implement Timeouts
swift
// ✅ Good: Set timeout
let timeout = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { _ in
self.runAuction() // Run with available bids
}
// ❌ Bad: No timeout (auction may never complete)Troubleshooting
Issue: getBid() Returns nil
Cause: Ad not loaded or load failed
Solution:
swift
// Always check if ad and bid exist
guard let ad = operaAd,
let bid = ad.getBid() else {
print("Ad or bid not available")
return
}Issue: Notification Methods Have No Effect
Cause: Called more than once or on wrong thread
Solution:
swift
// Ensure called only once and on main thread
if !notificationSent {
DispatchQueue.main.async {
bid.notifyWin(secondPrice: price, bidderName: name)
self.notificationSent = true
}
}Issue: Lower eCPM Than Expected
Cause: Test mode or limited demand
Solution:
- Ensure
useTestAd = falsein production - Check placement ID is correct
- Verify ad inventory availability
- Review floor price settings
Support
Document Version: 2.12.0 Last Updated: 2026-08-03 SDK Version: 2.12.0
