iOSに広告を表示するためのライブラリAdMobは公式にSwiftUIに対応しています。
Bannerのコード
広告にはいろいろな種別がありますが、バナーのコードを見ていきます。
下記のコードは、公式に沿って実装し、ビルドが通るようにしたコードになります。
protocol BannerViewControllerWidthDelegate: AnyObject {
func bannerViewController(_ bannerViewController: BannerViewController, didUpdate width: CGFloat)
}
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
)
}
}
}
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()
}
}
横画面表示
横画面にすると、BannerViewControllerでviewWillTransitionが動作し、delegate経由で取得した最適なバナーサイズをviewWidthとして取得します。
その際にupdateUIViewControllerが動作し、バナーサイズを変更して再ロードが行われるため、横画面にしても最適なバナーサイズの広告が表示されます。
Bannerの問題点
しかし、このコードにはいくつかの不具合があります。
バナーが最下部に表示されない
var body: some View {
VStack {
Text("Contents")
Spacer()
BannerView()
}
}
例えば上記のコードを用意します。
これは画面上部にコンテンツを用意し、最下部にバナー表示を意図したコードにしていますが、実際はコンテンツにくっつく形で表示されます。
これを防ぐために、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)
}
...
}