Appearance
Opera Ads iOS SDK - Client Bidding (C2S)
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?.load(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 loadWithPlacementId:@"s14198264979520"];
}
- (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: .HIGHER_BID,
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:OpAdxLossReasonHigherBid
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: .HIGHER_BID, 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:OpAdxLossReasonHigherBid
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?.loadAd(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: .HIGHER_BID, 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?.load(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: .HIGHER_BID, winnerPrice: winnerEcpm, winnerBidder: "Winner")
}
}
}Advanced Topics
Loss Reasons
When notifying a loss, provide accurate loss reason:
Swift:
swift
public enum OpAdxLossReason: Int {
case INTERNAL_ERROR = 1 // Internal SDK error
case BELOW_FLOOR = 2 // Bid below floor price
case HIGHER_BID = 3 // Lost to higher bid
}
// Usage
bid.notifyLose(
lossReason: .HIGHER_BID,
winnerPrice: 1.50,
winnerBidder: "Network X"
)Objective-C:
objective-c
// Loss reason enum
typedef NS_ENUM(NSInteger, OpAdxLossReason) {
OpAdxLossReasonInternalError = 1,
OpAdxLossReasonBelowFloor = 2,
OpAdxLossReasonHigherBid = 3
};
// Usage
[bid notifyLoseWithLossReason:OpAdxLossReasonHigherBid
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: .BELOW_FLOOR,
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?.load(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: .HIGHER_BID,
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: .HIGHER_BID, 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.9.0 Last Updated: 2026-03-24 SDK Version: 2.9.0
