Skip to main content

Setting up an App Open Ad

App Open Ads have two possible flows designed to be used together: Cold start and Resuming from background. For both use cases, we use the CIAppOpenAdManager API, which has loadAd() and show() methods to handle the App Open ad lifecycle. This is a singleton object which can be accessed via CIAppOpenAdManager.shared.

Cold start (Splash screen)

The first approach allows you monetise the loading screen of your app. When the user opens the app, the splash screen is displayed. While your app is loading, our SDK will load an Ad. If the ad is loaded in time you can control whether the ad is displayed before the home screen of your app is displayed. The example below is taken from our demo app:
import UIKit
import CIMobileSDK

enum SplashState {
    case loading
    case adReady
    case navigateToHome
}

@MainActor
final class SplashViewModel {

    private let adLoadTimeout: Duration = .seconds(5)
    private let adManager: CIAppOpenAdManager

    // 1. Monitor state changes in the UI
    var onStateChanged: ((SplashState) -> Void)?

    private var state: SplashState = .loading {
        didSet { onStateChanged?(state) }
    }

    init(adManager: CIAppOpenAdManager) {
        // 2. Store an instance of CIAppOpenAdManager
        self.adManager = adManager
    }

    func load() {
        // 3. Initialise the SDK
        let configuration = CIMobileAdsConfiguration.Builder(publisherId: "<PUBLISHER-ID>")
            .setDebugFeature(enabled: true)
            .enableAppOpen(placementID: "<PLACEMENT-ID>")
            .build()
            
        Task {
            let status = try await CIMobileAds.shared.initialise(
                configuration: configuration
            )
            switch status {
            case .succeeded:
                print("CIMobileSDK initialization succeeded")
                let adLoaded = await loadAdWithTimeout()
                Task { @MainActor in
                    state = adLoaded ? .adReady : .navigateToHome
                }
            case .failed:
                print("CIMobileSDK initialization failed")
                navigateToHome()
            }
        }
        
        Task {
            for await event in adManager.state {
                if event == .dismissed {
                    navigateToHome()
                }
            }
        }
    }
    
    private func loadAdWithTimeout() async -> Bool {
        // 4. Call loadAd() on the CIAppOpenManager instance
        adManager.loadAd()

        let adManager = self.adManager
        
        return await withTaskGroup(of: Bool?.self) { group in
            group.addTask {
                for await state in adManager.state {
                    switch state {
                    case .loaded: return true
                    case .loadFailed: return false
                    default:
                        continue
                    }
                }
                return false
            }
            group.addTask {
                try? await Task.sleep(for: self.adLoadTimeout)
                return nil
            }
            for await result in group {
                group.cancelAll()
                return result ?? false
            }
            return false
        }
    }
    
    private func navigateToHome() {
        Task { @MainActor [weak self] in
            self?.state = .navigateToHome
        }
    }

    func showAd() {
        // 5. Call the CIAppOpenAdManager to show the Ad.
        adManager.show()
    }
}
Let’s break down this code:
  1. In this simple example, we use a SplashState enumeration to communicate our state changes back to the UI, here this is done using a callback.
  2. In our ViewModel we store an instance of CIAppOpenAdManager.
  3. Enabling App Open ads is achieved by using enableAppOpen(placementID: String) in the SDK configuration builder. Passing in the placement ID which will be supplied to you by Content Ignite.
  4. In this example we use a simple timeout to simulate the loading of the app and attempt to load an ad before the timeout is reached.
  5. Calling the show() method on CIAppOpenManager to show the ad.
From the UI we can control when the ad is shown by responding to the state updates. We make use of a AppRouter, which in this example holds a variable to track if the splash screen has been completed, which is updated by calling `navigateToHome():
import UIKit

final class SplashViewController: UIViewController {

    private let viewModel: SplashViewModel
    private let router: AppRouter

    ...
    private func bindViewModel() {
        viewModel.onStateChanged = { [weak self] state in
            guard let self else { return }
            switch state {
            case .loading:
                ...
            case .adReady:
                ...
                self.viewModel.showAd()
            case .navigateToHome:
                self.router.navigateToHome()
            }
        }
    }
}

Resuming from background

For this approach we listen to the sceneWillEnterForeground from UISceneDelegate. Additionally we use AppRouter which handles the navigation and holds a splashCompleted Boolean value.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    private var router: AppRouter?
    private let adManager = CIAppOpenAdManager.shared

    ...
    func sceneWillEnterForeground(_ scene: UIScene) {
        guard let router, router.splashCompleted else { return }

        Task { @MainActor in
            adManager.show()
        }
    }
}
See the full examples: SPM | CocoaPods.