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.

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 SwiftUI
import Combine
import CIMobileSDK

enum SplashState {
    case loading
    case adReady
    case navigateToHome
}

@MainActor
final class SplashViewModel: ObservableObject {

    // 1. Monitor state changes in the UI
    @Published private(set) var state: SplashState = .loading
    
    private let adLoadTimeout: Duration = .seconds(5)
    private var loadContinuation: CheckedContinuation<Bool, Never>?

    // 2. Store an instance of CIAppOpenAdManager
    private let adManager: CIAppOpenAdManager

    init(adManager: 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 navigateToHome() {
        Task { @MainActor in
            self.state = .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
        }
    }

    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, an ObservableObject which in this example holds a variable to track if the splash screen has been completed, which is updated by calling `navigateToHome():
import SwiftUI
import CIMobileSDK

@MainActor
final class AppRouter: ObservableObject {
    @Published var splashCompleted = false

    func navigateToHome() {
        splashCompleted = true
    }
}

struct SplashView: View {

    @StateObject private var viewModel: SplashViewModel
    @EnvironmentObject private var router: AppRouter

    init(adManager: CIAppOpenAdManager) {
        _viewModel = StateObject(wrappedValue: SplashViewModel(adManager: adManager))
    }

    var body: some View {
        ZStack {
            ...
        }
        .onChange(of: viewModel.state, initial: true) { oldState, newState in
            switch newState {
            case .adReady:
                viewModel.showAd()
            case .navigateToHome:
                router.navigateToHome()
            case .loading:
                viewModel.load()
            }
        }
    }
}

Resuming from background

For this approach we need to listen to the application ScenePhase. To achieve this, we use a property wrapper inside the App class. Additionally we the AppRouter we saw earlier to update the view hierarchy:
import SwiftUI
import CIMobileSDK

@main
struct AppOpenSwiftUIApp: App {
    
    @StateObject private var router = AppRouter()
    @Environment(\.scenePhase) private var scenePhase

    private let adManager = CIAppOpenAdManager.shared
    
    var body: some Scene {
        WindowGroup {
            Group {
                if router.splashCompleted {
                    HomeView()
                } else {
                    SplashView(adManager: adManager)
                }
            }
            .environmentObject(router)
            .onChange(of: scenePhase) {
                guard scenePhase == .active, router.splashCompleted else { return }
                
                if adManager.isAdAvailable() {
                    adManager.show()
                } else {
                    adManager.loadAd()
                }
            }
        }
    }
}
See the full examples: SPM | CocoaPods.