First Look
Give CloudX the first chance to fill an ad placement, then fall back to your existing mediation setup
First Look gives CloudX the first chance to fill a placement while preserving your existing mediation setup as the fallback path. Start with one placement, verify load and show behavior, then expand to more placements.
This example uses an AdMob interstitial fallback. The same pattern applies to other fallback mediators: load CloudX first, load the fallback only if CloudX does not load, and keep the app flow moving when neither source is ready.
import CloudXCore
import GoogleMobileAds
import UIKit
@MainActor
final class FirstLookInterstitialController: NSObject {
enum Source {
case cloudX
case adMob
}
protocol Listener: AnyObject {
func firstLookInterstitialController(_ controller: FirstLookInterstitialController, didLoad source: Source)
func firstLookInterstitialController(_ controller: FirstLookInterstitialController, didFailToLoad source: Source, message: String?)
func firstLookInterstitialController(_ controller: FirstLookInterstitialController, didShow source: Source)
func firstLookInterstitialController(_ controller: FirstLookInterstitialController, didFailToShow source: Source, message: String?)
func firstLookInterstitialController(_ controller: FirstLookInterstitialController, didClose source: Source)
func firstLookInterstitialController(_ controller: FirstLookInterstitialController, didClick source: Source)
}
weak var listener: Listener?
private weak var viewController: UIViewController?
private let cloudXInterstitial: CLXInterstitial
private let adMobAdUnitId: String
private var adMobInterstitial: InterstitialAd?
private var isLoadingCloudX = false
private var isLoadingAdMob = false
init?(
viewController: UIViewController,
cloudXAdUnitId: String,
adMobAdUnitId: String
) {
guard let cloudXInterstitial = CloudXCore.shared.createInterstitial(adUnitId: cloudXAdUnitId) else {
return nil
}
self.viewController = viewController
self.cloudXInterstitial = cloudXInterstitial
self.adMobAdUnitId = adMobAdUnitId
super.init()
self.cloudXInterstitial.delegate = self
}
func load() {
guard !isLoadingCloudX,
!isLoadingAdMob,
!cloudXInterstitial.isReady,
adMobInterstitial == nil else {
return
}
isLoadingCloudX = true
cloudXInterstitial.load()
}
func show() -> Bool {
guard let viewController else {
return false
}
if cloudXInterstitial.isReady {
cloudXInterstitial.show(from: viewController)
return true
}
return showAdMobFallback(from: viewController)
}
func destroy() {
cloudXInterstitial.destroy()
adMobInterstitial = nil
listener = nil
}
private func loadAdMobFallback() {
guard !isLoadingAdMob, adMobInterstitial == nil else {
return
}
isLoadingAdMob = true
InterstitialAd.load(with: adMobAdUnitId, request: Request()) { [weak self] ad, error in
guard let self else { return }
self.isLoadingAdMob = false
if let error {
self.adMobInterstitial = nil
self.listener?.firstLookInterstitialController(self, didFailToLoad: .adMob, message: error.localizedDescription)
return
}
guard let ad else {
self.adMobInterstitial = nil
self.listener?.firstLookInterstitialController(self, didFailToLoad: .adMob, message: nil)
return
}
ad.fullScreenContentDelegate = self
self.adMobInterstitial = ad
self.listener?.firstLookInterstitialController(self, didLoad: .adMob)
}
}
private func showAdMobFallback(from viewController: UIViewController) -> Bool {
guard let adMobInterstitial else {
return false
}
adMobInterstitial.present(from: viewController)
return true
}
}
extension FirstLookInterstitialController: CLXInterstitialDelegate {
func didLoad(_ ad: CLXAd) {
isLoadingCloudX = false
listener?.firstLookInterstitialController(self, didLoad: .cloudX)
}
func didFailToLoadAd(_ adUnitId: String, error: CLXError) {
isLoadingCloudX = false
loadAdMobFallback()
}
func didDisplay(_ ad: CLXAd) {
listener?.firstLookInterstitialController(self, didShow: .cloudX)
}
func didFailToDisplay(_ ad: CLXAd, error: CLXError) {
if let viewController, showAdMobFallback(from: viewController) {
return
}
listener?.firstLookInterstitialController(self, didFailToShow: .cloudX, message: error.localizedDescription)
}
func didHide(_ ad: CLXAd) {
listener?.firstLookInterstitialController(self, didClose: .cloudX)
}
func didClick(_ ad: CLXAd) {
listener?.firstLookInterstitialController(self, didClick: .cloudX)
}
}
extension FirstLookInterstitialController: FullScreenContentDelegate {
func adWillPresentFullScreenContent(_ ad: FullScreenPresentingAd) {
adMobInterstitial = nil
listener?.firstLookInterstitialController(self, didShow: .adMob)
}
func adDidDismissFullScreenContent(_ ad: FullScreenPresentingAd) {
adMobInterstitial = nil
listener?.firstLookInterstitialController(self, didClose: .adMob)
}
func ad(_ ad: FullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
adMobInterstitial = nil
listener?.firstLookInterstitialController(self, didFailToShow: .adMob, message: error.localizedDescription)
}
func adDidRecordClick(_ ad: FullScreenPresentingAd) {
listener?.firstLookInterstitialController(self, didClick: .adMob)
}
}Use show() at the placement moment. If it returns false, neither CloudX nor the fallback had a ready ad, so continue the app flow without showing an ad.
if firstLookInterstitial?.show() != true {
continueToNextScreen()
}After a show, close, or terminal failure, call load() again when your app is ready to prepare the next placement opportunity.
Demo App
The CloudX iOS demo app runs this flow on device, in Swift and Objective-C. It structures the flow as three files, the controller and one source per SDK, and its host screen retries a failed load or show with a 2 to 60 second backoff.
FirstLookInterstitialController.swift
Gives CloudX the first load attempt. AdMob loads only after CloudX fails or is unavailable.
CloudXFirstLookSource.swift
CloudX’s leg of the flow.
AdMobFirstLookSource.swift
The lazy AdMob fallback. A loaded ad shows once and expires after one hour.
FirstLookViewController.swift
Brings up both SDKs, retries with backoff and wires the Show button. This is demo-only layout; take the rules, not the file.
Copy the first three together. The two sources log through the demo’s DemoAppLogger; swap in your own logging when you copy them. The Objective-C versions are in demo-app-objc/CloudXObjCRemotePods/Ads/FirstLook/.