はじめに
2019年11月20日現在、英日でググっても AdMob の Native Ads を SwiftUI で表示する記事はないので同じことをやろうとする人のためにやり方を書いておこうと思う。
AdMobの用意
以下のドキュメントを読んで Xcode のプロジェクトが AdMob にリクエストができる状態にする。
Get Started | Firebase
Native Templates iOSをプロジェクトに取り込む
以下のリンクから Google が用意してる native ads のテンプレートを git clone
するかダウンロードして Xcode のプロジェクトに nativetemplates folder 以下のソースコードと xib file を追加する。
ソースコードと xib file を Xcode に追加するときの注意点はフォルダーをドラッグせずにソードコードとxibのファイルを選択してドラッグしないとビルドターゲットにそれらのファイルが入らないのと Bridging-Header を作るかどうかを Xcode が聞いてこない。
テンプレートのコードは Objective-C なので Swift 用の Bridging-Header を用意する。
Bridging-Header で以下のように import しておく。
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "GADTMediumTemplateView.h"
#import "GADTSmallTemplateView.h"
#import "GADTTemplateView.h"
SwiftUIのNative Adsを表示する view を作る
まず GADTMediumTemplateView を表示する UIViewController を SwiftUI に持ってくるために UIViewControllerRepresentable を使って NativeAdsViewController を作る。
GADUnifiedNativeAdLoaderDelegate に適応するために NSObject を継承した NativeAdsViewController を用意して、さらに UIViewControllerRepresentable に適応する。
final class NativeAdsViewController: NSObject, UIViewControllerRepresentable {}
makeUIViewController(context:)
を実装していく。
UIViewController を用意してその view に GADTMediumTemplateView を追加していく。
func makeUIViewController(context: UIViewControllerRepresentableContext<NativeAdsViewController>) -> UIViewController {
let templateView = GADTMediumTemplateView()
self.templateView = templateView
let viewController = UIViewController()
viewController.view.addSubview(templateView)
templateView.addHorizontalConstraintsToSuperviewWidth()
templateView.addVerticalCenterConstraintToSuperview()
return viewController
}
GADAdLoader で広告をロードする。
func makeUIViewController(context: UIViewControllerRepresentableContext<NativeAdsViewController>) -> UIViewController {
let templateView = GADTMediumTemplateView()
self.templateView = templateView
let viewController = UIViewController()
viewController.view.addSubview(templateView)
templateView.addHorizontalConstraintsToSuperviewWidth()
templateView.addVerticalCenterConstraintToSuperview()
#if DEBUG
let nativeAdvanced = "ca-app-pub-3940256099942544/3986624511"
// let nativeAdvancedVideo = "ca-app-pub-3940256099942544/2521693316"
let adUnitID = nativeAdvanced
#else
let adUnitID = self.adUnitID
#endif
let rootViewController = UIApplication.shared.windows.first?.rootViewController
let loader = GADAdLoader(adUnitID: adUnitID, rootViewController: rootViewController, adTypes: [GADAdLoaderAdType.unifiedNative], options: nil)
loader.delegate = self
adLoader = loader
loader.load(GADRequest())
return viewController
}
GADUnifiedNativeAdLoaderDelegate の adLoader(_:didReceive nativeAd:) で受け取った広告を templateView に渡して広告を view に表示する。
これで広告は表示される。
extension NativeAdsViewController: GADUnifiedNativeAdLoaderDelegate {
func adLoader(_ adLoader: GADAdLoader, didReceive nativeAd: GADUnifiedNativeAd) {
templateView?.nativeAd = nativeAd
}
func adLoader(_ adLoader: GADAdLoader, didFailToReceiveAdWithError error: GADRequestError) {}
}
しかし、今の NativeAdsViewController を SwiftUI の ScrollView に入れると高さが0になってしまう。
GADTMediumTemplateView は auto layout でレイアウトされていて、view の高さは広告のテキスト、mediaView の16:9のアスペクト比などから計算されるので一意に view のアスペクト比や高さを決めることができないので NativeAdsViewController を横方向に最大限拡がって高さをそれに合わせて fit するように NativeAdsViewController に frame() をセットするために auto layout で計算された view の高さを使う必要がある。
まず GADTMediumTemplateView のサイズを SwiftUI に持ってくるために SizeModel: ObservableObject を定義する。
ObservableObject にしてるのは height が更新された時に SwiftUI に広告の view を再レンダしてもらうためだ。
final class SizeModel: ObservableObject {
@Published var width: CGFloat = 355
@Published var height: CGFloat = 370
}
次に templateView: GADTMediumTemplateView? の .bounds の変化を Combine で subscribe して sizeModel を更新していく。
subscribeUpdateSizeModel() を makeUIViewController(context:) 内で呼んでおく。
private func subscribeUpdateSizeModel() {
templateView?.publisher(for: \.bounds)
.sink(receiveValue: { (bounds) in
self.sizeModel.width = bounds.width
self.sizeModel.height = bounds.height
})
.store(in: &cancellables)
}
次に NativeAdsView.swift を用意する。
ここで NativeAdsViewController.SizeModel を @ObservedObject として持ち、sizeModel.height を NativeAdsViewController に frame() で適用する。
これで auto layout された高さの値が適用される。
import SwiftUI
struct NativeAdsView: View {
let adUnitID: String
private @ObservedObject var sizeModel = NativeAdsViewController.SizeModel()
var body: some View {
NativeAdsViewController(adUnitID: adUnitID, sizeModel: sizeModel)
.frame(height: sizeModel.height)
}
}
struct NativeAdsView_Previews: PreviewProvider {
static var previews: some View {
NativeAdsView(adUnitID: "ca-app-pub-3940256099942544/3986624511")
}
}
NativeAdsViewController.swift
import SwiftUI
import Combine
final class NativeAdsViewController: NSObject, UIViewControllerRepresentable {
final class SizeModel: ObservableObject {
@Published var width: CGFloat = 355
@Published var height: CGFloat = 370
}
var adLoader: GADAdLoader?
var templateView: GADTMediumTemplateView?
let adUnitID: String
let sizeModel: SizeModel
private var cancellables = Set<AnyCancellable>()
init(adUnitID: String, sizeModel: SizeModel) {
self.adUnitID = adUnitID
self.sizeModel = sizeModel
}
func makeUIViewController(context: UIViewControllerRepresentableContext<NativeAdsViewController>) -> UIViewController {
let templateView = GADTMediumTemplateView()
self.templateView = templateView
let viewController = UIViewController()
viewController.view.addSubview(templateView)
templateView.addHorizontalConstraintsToSuperviewWidth()
templateView.addVerticalCenterConstraintToSuperview()
subscribeUpdateSizeModel()
#if DEBUG
let nativeAdvanced = "ca-app-pub-3940256099942544/3986624511"
// let nativeAdvancedVideo = "ca-app-pub-3940256099942544/2521693316"
let adUnitID = nativeAdvanced
#else
let adUnitID = self.adUnitID
#endif
let rootViewController = UIApplication.shared.windows.first?.rootViewController
let loader = GADAdLoader(adUnitID: adUnitID, rootViewController: rootViewController, adTypes: [GADAdLoaderAdType.unifiedNative], options: nil)
loader.delegate = self
adLoader = loader
loader.load(GADRequest())
return viewController
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<NativeAdsViewController>) {}
private func subscribeUpdateSizeModel() {
templateView?.publisher(for: \.bounds)
.sink(receiveValue: { (bounds) in
self.sizeModel.width = bounds.width
self.sizeModel.height = bounds.height
})
.store(in: &cancellables)
}
}
extension NativeAdsViewController: GADUnifiedNativeAdLoaderDelegate {
func adLoader(_ adLoader: GADAdLoader, didReceive nativeAd: GADUnifiedNativeAd) {
templateView?.nativeAd = nativeAd
}
func adLoader(_ adLoader: GADAdLoader, didFailToReceiveAdWithError error: GADRequestError) {}
}
おわりに
最後まで読んでくださってありがとうございます。
UIViewControllerRepresentable の内包された view の auto layout で変化する size を簡単で綺麗に ideal size にできないのが今のところ不便ですね。
もしもっと綺麗な方法があるなら教えてください。