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.