LoginSignup
12
12

More than 3 years have passed since last update.

AdMobのNative AdsをSwiftUIで表示する

Posted at

はじめに

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 を追加する。

Native Templates iOS

ソースコードと xib file を Xcode に追加するときの注意点はフォルダーをドラッグせずにソードコードとxibのファイルを選択してドラッグしないとビルドターゲットにそれらのファイルが入らないのと Bridging-Header を作るかどうかを Xcode が聞いてこない。

テンプレートのコードは Objective-C なので Swift 用の Bridging-Header を用意する。

Bridging-Header で以下のように import しておく。

_project_name_-Bridging-Header.h
//
//  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 された高さの値が適用される。

NativeAdsView.swift
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")
    }
}

Screen Shot.png

NativeAdsViewController.swift

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 にできないのが今のところ不便ですね。
もしもっと綺麗な方法があるなら教えてください。

12
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
12