2
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SwiftUIで実装した公式AdMob Bannerのコードを修正する

Last updated at Posted at 2023-11-17

iOSに広告を表示するためのライブラリAdMobは公式にSwiftUIに対応しています。

Bannerのコード

広告にはいろいろな種別がありますが、バナーのコードを見ていきます。
下記のコードは、公式に沿って実装し、ビルドが通るようにしたコードになります。

swift BannerViewControllerWidthDelegate.swift
protocol BannerViewControllerWidthDelegate: AnyObject {
    func bannerViewController(_ bannerViewController: BannerViewController, didUpdate width: CGFloat)
}
swift BannerViewController.swift
class BannerViewController: UIViewController {
    weak var delegate: BannerViewControllerWidthDelegate?

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        // Tell the delegate the initial ad width.
        delegate?.bannerViewController(
            self, didUpdate: view.frame.inset(by: view.safeAreaInsets).size.width
        )
    }

    override func viewWillTransition(
        to _: CGSize, with coordinator: UIViewControllerTransitionCoordinator
    ) {
        coordinator.animate { _ in
            // do nothing
        } completion: { _ in
            // Notify the delegate of ad width changes.
            self.delegate?.bannerViewController(
                self, didUpdate: self.view.frame.inset(by: self.view.safeAreaInsets).size.width
            )
        }
    }
}
swift BannerView.swift
struct BannerView: UIViewControllerRepresentable {
    @State private var viewWidth: CGFloat = .zero
    private let bannerView = GADBannerView()
    private let adUnitID = "ca-app-pub-3940256099942544/2934735716"

    func makeUIViewController(context: Context) -> some UIViewController {
        let bannerViewController = BannerViewController()
        bannerView.adUnitID = adUnitID
        bannerView.rootViewController = bannerViewController
        bannerView.delegate = context.coordinator
        bannerViewController.view.addSubview(bannerView)
        // Tell the bannerViewController to update our Coordinator when the ad
        // width changes.
        bannerViewController.delegate = context.coordinator

        return bannerViewController
    }

    func updateUIViewController(_: UIViewControllerType, context _: Context) {
        guard viewWidth != .zero else { return }

        // Request a banner ad with the updated viewWidth.
        bannerView.adSize = GADCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(viewWidth)
        bannerView.load(GADRequest())
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, BannerViewControllerWidthDelegate, GADBannerViewDelegate {
        let parent: BannerView

        init(_ parent: BannerView) {
            self.parent = parent
        }

        // MARK: - BannerViewControllerWidthDelegate methods

        func bannerViewController(_: BannerViewController, didUpdate width: CGFloat) {
            // Pass the viewWidth from Coordinator to BannerView.
            parent.viewWidth = width
        }

        // MARK: - GADBannerViewDelegate methods

        func bannerViewDidReceiveAd(_: GADBannerView) {
            print("\(#function) called")
        }

        func bannerView(_: GADBannerView, didFailToReceiveAdWithError _: Error) {
            print("\(#function) called")
        }

        func bannerViewDidRecordImpression(_: GADBannerView) {
            print("\(#function) called")
        }

        func bannerViewWillPresentScreen(_: GADBannerView) {
            print("\(#function) called")
        }

        func bannerViewWillDismissScreen(_: GADBannerView) {
            print("\(#function) called")
        }

        func bannerViewDidDismissScreen(_: GADBannerView) {
            print("\(#function) called")
        }
    }
}

このコードに沿ってバナーを表示すると、実際にバナーが表示されます。

struct AdMobAdView: View {
    var body: some View {
        BannerView()
    }
}

Simulator Screenshot - iPhone 15 - 2023-11-17 at 08.43.44.png

横画面表示

横画面にすると、BannerViewControllerでviewWillTransitionが動作し、delegate経由で取得した最適なバナーサイズをviewWidthとして取得します。
その際にupdateUIViewControllerが動作し、バナーサイズを変更して再ロードが行われるため、横画面にしても最適なバナーサイズの広告が表示されます。

Simulator Screenshot - iPhone 15 - 2023-11-17 at 08.43.39.png

Bannerの問題点

しかし、このコードにはいくつかの不具合があります。

バナーが最下部に表示されない

    var body: some View {
        VStack {
            Text("Contents")
            Spacer()
            BannerView()
        }
    }

例えば上記のコードを用意します。
これは画面上部にコンテンツを用意し、最下部にバナー表示を意図したコードにしていますが、実際はコンテンツにくっつく形で表示されます。

Simulator Screenshot - iPhone 15 - 2023-11-17 at 08.57.12.png

これを防ぐために、BannerViewのmakeUIViewControllerにNSLayoutConstraintで制約を追加することで対応します。
公式のGithubでもこの対応がとられています。

    func makeUIViewController(context: Context) -> some UIViewController {
        let bannerViewController = BannerViewController()
        bannerView.adUnitID = adUnitID
        bannerView.rootViewController = bannerViewController
        bannerView.delegate = context.coordinator
        bannerView.translatesAutoresizingMaskIntoConstraints = false
        bannerViewController.view.addSubview(bannerView)
        // Constrain GADBannerView to the bottom of the view.
        NSLayoutConstraint.activate([
            bannerView.bottomAnchor.constraint(
                equalTo: bannerViewController.view.safeAreaLayoutGuide.bottomAnchor),
            bannerView.centerXAnchor.constraint(equalTo: bannerViewController.view.centerXAnchor),
        ])
        bannerViewController.delegate = context.coordinator
        
        return bannerViewController
    }

iPadで広告が表示されない

新規のアプリをSwiftUIで作成した場合、Scene対応されることになりますが、その場合、iPadでバナー広告が表示されません。
GADRequestにwindowSceneを設定する必要があります。

AppDelegateとSceneDelegateをEnvironmentObjectに対応する方法があるので、SceneDelegate経由で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
    }
}

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
    }
}

用意したsceneDelegateをGADRequestに設定することでiPadでも広告が表示できるようになりました。

struct BannerView: UIViewControllerRepresentable {
    @State private var viewWidth: CGFloat = .zero
    // SceneDelegateを追加
    @EnvironmentObject private var sceneDelegate: MySceneDelegate
    private let bannerView = GADBannerView()
    private let adUnitID = "ca-app-pub-3940256099942544/2934735716"

    ...
    
    func updateUIViewController(_: UIViewControllerType, context _: Context) {
        guard viewWidth != .zero else { return }
        
        // Request a banner ad with the updated viewWidth.
        bannerView.adSize = GADCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(viewWidth)
        let request = GADRequest()
        request.scene = sceneDelegate.windowScene
        bannerView.load(request)
    }
    
    ...
}

Simulator Screenshot - iPad (10th generation) - 2023-11-17 at 09.23.56.png

2
8
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
2
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?