重要!
SwiftUIの公式ドキュメントが出ました。
以下の記述は公式ドキュメントが出る前の内容になりますので、ご認識ください。
要約
SwiftUIでUIViewRepresentable
を用いてAdMobのネイティブ広告を表示します。
はじめに
SwiftUIでAdMobのネイティブ広告を表示します。いわゆるSwiftUI life cycleでの表示例です。
AdMobのネイティブを含む広告はSwiftUIではサンプルがないので、表示できた実装例を示します。
公式での実装ではないので何かしら問題が起こる可能性があります。
AdMobの導入が済んでいる事が前提となります。
https://developers.google.com/admob/ios/quick-start?hl=ja
コードと説明
App
AdMobはAppDelegateでの初期化が必要なので、AppでUIApplicationDelegateAdaptor
を実装してAdMobの初期化対応を行います。
後程SceneDelegateの対応も必要になるので実装します。
SceneDelegateではwindowやwindowSceneを取得できるように用意しておきます。
このAppDelegateやSceneDelegateの実装は公式の実装を参考にしています。
import SwiftUI
import GoogleMobileAds
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(MyAppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class MyAppDelegate: NSObject, UIApplicationDelegate, ObservableObject {
func application(_ application: UIApplication, didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
GADMobileAds.sharedInstance().start(completionHandler: nil)
return true
}
}
class MySceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObject {
var windowScene: UIWindowScene?
var window: UIWindow? {
windowScene?.keyWindow
}
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
windowScene = scene as? UIWindowScene
window = windowScene?.keyWindow
}
}
extension MyAppDelegate {
func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
let configuration = UISceneConfiguration(
name: nil,
sessionRole: connectingSceneSession.role)
if connectingSceneSession.role == .windowApplication {
configuration.delegateClass = MySceneDelegate.self
}
return configuration
}
}
ContentView.swift
実際に表示するView部分では、こちらのコード例では広告のロードを表示時に行い、広告用のデータGADNativeAd
が取得できたらネイティブ広告を表示するようにしています。
ネイティブ広告のロード周りはNativeAdModelとしてObservableObjectで実装して、結果を受け取るようにしています。
ポイントとしてはEnvironmentObject
としてsceneDelegateを定義している部分で、先ほどのUIApplicationDelegateAdaptorのドキュメントにApp delegateの実装にObservableObjectが実装されていれば、EnvironmentObjectとして使える
というものがあります。
If your app delegate conforms to the ObservableObject protocol, as in the example above, then SwiftUI puts the delegate it creates into the Environment. You can access the delegate from any scene or view in your app using the EnvironmentObject property wrapper
sceneDelegateも同様にEnvironmentObjectとして使えるので、UIWindowSceneを持たせて利用をしています。
As with the app delegate, if you make your scene delegate an observable object, SwiftUI automatically puts it in the Environment, from where you can access it with the EnvironmentObject property wrapper, and create bindings to its published properties.
import SwiftUI
import GoogleMobileAds
struct ContentView: View {
@EnvironmentObject private var sceneDelegate: MySceneDelegate
@StateObject private var model = NativeAdModel()
var body: some View {
VStack {
Text("Native Ad")
if let nativeAd = model.nativeAd {
NativeAdView(nativeAd: nativeAd)
}
}
.onAppear(perform: loadAd)
}
private func loadAd() {
model.load(windowScene: sceneDelegate.windowScene, rootViewController: sceneDelegate.window?.rootViewController)
}
}
class NativeAdModel: NSObject, ObservableObject, GADNativeAdLoaderDelegate {
@Published var nativeAd: GADNativeAd?
private var adLoader: GADAdLoader?
func load(windowScene: UIWindowScene?,
rootViewController: UIViewController?) {
let adLoader = GADAdLoader(adUnitID: "ca-app-pub-3940256099942544/3986624511", rootViewController: rootViewController, adTypes: [.native], options: nil)
self.adLoader = adLoader
adLoader.delegate = self
let request = GADRequest()
request.scene = windowScene
adLoader.load(request)
}
func adLoader(_ adLoader: GADAdLoader, didReceive nativeAd: GADNativeAd) {
self.nativeAd = nativeAd
}
func adLoader(_ adLoader: GADAdLoader, didFailToReceiveAdWithError error: Error) {
}
}
NativeAdView.swift (UIViewRepresentable)
最後に広告部分をUIViewRepresentableで実装します。
GADNativeAdを受け取ってViewを実装します。
基本的には、AdMobの広告を表示する実装と同じく、xibからの生成から各種パラメータの設定を行なっています。
注意点
xcframeworkに含まれているIBOutlet付きのヘッダーはInterface builderで認識できず、アウトレット接続ができない問題があるようです。
GADNativeAdViewに関しても同様に問題があり、自分のプロジェクトにGADNativeAdView.hをコピーするのが対処法になるようです。
https://groups.google.com/g/google-admob-ads-sdk/c/YKCW9Be2_cs/m/FScvu4XnAQAJ
import GoogleMobileAds
struct NativeAdView: UIViewRepresentable {
let nativeAd: GADNativeAd
func makeUIView(context: Context) -> GADNativeAdView {
let nativeAdView: GADNativeAdView = Bundle.main.loadNibNamed("NativeAdView", owner: nil, options: nil)?.first as! GADNativeAdView
(nativeAdView.bodyView as? UILabel)?.text = nativeAd.body
nativeAdView.bodyView?.isHidden = nativeAd.body == nil
(nativeAdView.callToActionView as? UIButton)?.setTitle(nativeAd.callToAction, for: .normal)
nativeAdView.callToActionView?.isHidden = nativeAd.callToAction == nil
(nativeAdView.iconView as? UIImageView)?.image = nativeAd.icon?.image
nativeAdView.iconView?.isHidden = nativeAd.icon == nil
(nativeAdView.storeView as? UILabel)?.text = nativeAd.store
nativeAdView.storeView?.isHidden = nativeAd.store == nil
(nativeAdView.priceView as? UILabel)?.text = nativeAd.price
nativeAdView.priceView?.isHidden = nativeAd.price == nil
(nativeAdView.advertiserView as? UILabel)?.text = nativeAd.advertiser
nativeAdView.advertiserView?.isHidden = nativeAd.advertiser == nil
nativeAdView.callToActionView?.isUserInteractionEnabled = false
nativeAdView.nativeAd = nativeAd
return nativeAdView
}
func updateUIView(_ uiView: GADNativeAdView, context: Context) {
}
}