Compose MultiplatformでAdmobのインタースティシャル広告を表示させる

前提

  • Compose MultiplatformでiOSのみの開発をしています。
    (元々Jetpack Composeで作成していたAndroidアプリのiOS版を作成しよう&Multiplatformの実装について学ぼうということでCompose Multiplatformを導入しており、Android版は元々のままJetpack Composeのプロジェクトとして運用しております)
    そのため、iOS&Androidを同時開発している場合にはそのままだとちょっと動かない可能性もあります。
  • こうやったら動いたという趣旨の記事のため、ベストな記述方法ではない可能性があります。
  • Compose Multiplatformは日々発展中の技術のため、記事の記述内容はすぐ古くなっている可能性があります。(多分ちゃんとした公式ドキュメントが用意される日も近いと信じています)

上記諸々すみませんがご承知おきください。

この記事の内容

  • Compose MultiplatformでAdmob広告(インタースティシャル広告)を表示する方法についてのメモです。
  • Admobでのアプリ追加方法(実装で必要なアプリIDの取得)についてはこの記事では書いていません。
  • 最終的に以下の感じで広告が表示されます。

大まかな流れ

  1. スタートガイドに沿ってMobile Ads SDKをインポート
  2. Info.plist を更新する
  3. Mobile Ads SDK を初期化する(SwiftUIの例を参考に)
  4. SwiftUIでのフルスクリーンのAdmob導入の手順に従って広告表示のための実装を追加
  5. 共通処理側で広告表示の処理を呼び出す

developers.google.com developers.google.com

流れ詳細

1. Mobile Ads SDKのインポート(Swift Package Managerを利用)

  • CocoaPodsが推奨なので、そちらでやるのがベストかもしれません。
  • 筆者はCocoaPodsを使ったことがなく、Xcodeの画面からぽちぽちするだけでできるSwift Package Managerが簡単に感じたのでSwift Package Managerを使ったという単純な動機です

2. Info.plist を更新する

ソースコードを表示させて一気にペーストすると編集が簡単です。 なお、Admob用のアプリIDは、Admobの アプリ> アプリの設定 のアプリの情報欄から確認可能です。

3〜 SwiftUIの例を参考に実装していく&共通側で広告表示のための処理を呼びだす

最終的にiosAppは以下の感じの実装にしています(すみません力尽きて詳細まで書けておりません・・・)

iosApp/iosApp/ContentView.swift

import UIKit
import SwiftUI
import ComposeApp

struct ComposeView: UIViewControllerRepresentable {

    let loadAdMethod: () -> Void
    let showAdMethod: () -> Void

    init(loadAdMethod: @escaping () -> Void, showAdMethod: @escaping () -> Void) {
            self.loadAdMethod = loadAdMethod
            self.showAdMethod = showAdMethod
        }

    func makeUIViewController(context: Context) -> UIViewController {
        MainViewControllerKt.MainViewController(
            loadAdMethod: { self.loadAdMethod() },
            showAdMethod: { self.showAdMethod() }
        )
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}

struct ContentView: View {
    private let adCoordinator = AdCoordinator()

    var body: some View {
        ComposeView(
            loadAdMethod: { adCoordinator.loadAd() },
            showAdMethod: { adCoordinator.presentAd()}
        ).ignoresSafeArea(.keyboard) // Compose has own keyboard handler
    }
}

iosApp/iosApp/iOSApp.swift

import SwiftUI
import GoogleMobileAds

class AppDelegate: UIResponder, UIApplicationDelegate {

  func application(_ application: UIApplication,
      didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    GADMobileAds.sharedInstance().start(completionHandler: nil)

    return true
  }
}

@main
struct iOSApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

iosApp/iosApp/AdCoordinator.swift (広告の実装のために新規作成)

import ObjectiveC
import GoogleMobileAds

class AdCoordinator: NSObject, GADFullScreenContentDelegate {
  private var ad: GADInterstitialAd?
  private var isLoading: Bool = false    // この辺は、広告読み込み中なら再度読み込み処理は呼び出さないために記述してみています。
  private var adShowTiming: Int64 = Int64(Date().timeIntervalSince1970) + 20   // これは、広告を1回出したら連続して広告が出ないようにするためのインターバルを設けるために実装してみています。

  func loadAd() {
    if (isLoading) {
        return
    } else {
        isLoading = true
    }

    GADInterstitialAd.load(
      withAdUnitID: "ca-app-pub-3940256099942544/4411468910", request: GADRequest()
    ) { ad, error in
      if let error = error {
        return print("Failed to load ad with error: \(error.localizedDescription)")
      }

      self.ad = ad
      self.ad?.fullScreenContentDelegate = self
    }
  }

  func presentAd() {
        print("\(#function) called")
        if (isAdShowTiming()) {
            guard let fullScreenAd = ad else {
              return print("Ad wasn't ready")
            }
            // View controller is an optional parameter. Pass in nil.
            fullScreenAd.present(fromRootViewController: nil)
        }
  }

  func adDidRecordImpression(_ ad: GADFullScreenPresentingAd) {
      print("\(#function) called")
    }

    func adDidRecordClick(_ ad: GADFullScreenPresentingAd) {
      print("\(#function) called")
    }

    func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
      print("\(#function) called")
    }

    func adWillPresentFullScreenContent(_ ad: GADFullScreenPresentingAd) {
      print("\(#function) called")
    }


    func adWillDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
      print("\(#function) called")
    }

    func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
      print("\(#function) called")
      // ここは広告のアプリ上での表示が終わって閉じられた時に呼び出される関数なので、再度広告読み込みができるように自分で用意したフラグや変数やらを書き換えています
      isLoading = false   
      adShowTiming = Int64(Date().timeIntervalSince1970)  + 50
      loadAd()
    }

    func isAdShowTiming() -> Bool {
        return adShowTiming <= Int64(Date().timeIntervalSince1970)
    }
}

composeApp/src/iosMain/kotlin/MainViewController.kt

import androidx.compose.ui.window.ComposeUIViewController

fun MainViewController(
    loadAdMethod: () -> Unit,
    showAdMethod: () -> Unit,

) = ComposeUIViewController { App(
    loadAdMethod = loadAdMethod,
    showAdMethod = showAdMethod,
) }

composeApp/src/commonMain/kotlin/App.kt

@Composable
fun App(
    loadAdMethod: () -> Unit,
    showAdMethod: () -> Unit,
) {
    loadAdMethod()   // EUなどだと広告を読み込む前に広告配信の同意取得が必要です。EU等にもアプリを配信する場合はいきなりloadしないように注意してください。

    // 後略。必要な箇所でshowAdを呼び出せば広告が表示されます。そのあとはloadAdを呼び出して広告を再読み込みしてください。  

}

筆者の広告

  • 以下のAndroidゲーム(JetpackComposeで開発)のiOS版もCompose Multiplatformを利用して作成しようと試みています。
    play.google.com