TL;DR
How to implement AdMob inline adaptive banner ad in NavigationSplitView
's sidebar on iPad with SwiftUI, so it takes the entire width of its parent and the view's height adjusts to the ad's height?
Details
I'm trying to insert an AdMob inline adaptive banner ad in the sidebar of a NavigationSplitView
on iPad. As I'm using SwiftUI, I tried to replicate the implementation from this Google example. One problem is that the example is for an adaptive anchor banner ad—not what I'm looking for.
I've made a few attempts over the past few days, with varying results. I can't seem to make it truly adaptive. Two attempts that display the ad but with fixed size are presented below.
Attempt 1
struct InlineAdaptiveBannerAdView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> some UIViewController { let viewController = UIViewController() let adSize = GADInlineAdaptiveBannerAdSizeWithWidthAndMaxHeight(280, 150) let bannerView = GADBannerView(adSize: adSize) bannerView.adUnitID = "ca-app-pub-3940256099942544/2934735716" bannerView.rootViewController = viewController let request = GADRequest() request.scene = UIApplication.shared.connectedScenes.first as? UIWindowScene bannerView.load(request) viewController.view.addSubview(bannerView) return viewController } func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {} }
I didn't know how to get the width of the sidebar programmatically, so I set it to 320 with .navigationSplitViewColumnWidth()
and set the ad width to 280 (adjusted for padding).
This code displays the ad nicely (at least along X-axis), but the size is fixed.
Attempt 2
struct InlineAdaptiveBannerAdView: UIViewControllerRepresentable { // viewWidth is set to .zero in the Google sample @State private var viewWidth: CGFloat = CGFloat(280.0) 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 bannerView.translatesAutoresizingMaskIntoConstraints = false // Removed the constraints from the sample bannerViewController.view.addSubview(bannerView) bannerViewController.delegate = context.coordinator return bannerViewController } func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { guard viewWidth != .zero else { return } bannerView.adSize = GADInlineAdaptiveBannerAdSizeWithWidthAndMaxHeight(viewWidth, 150) let request = GADRequest() request.scene = UIApplication.shared.connectedScenes.first as? UIWindowScene bannerView.load(request) print("View height: \(uiViewController.view.frame.height)") } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, BannerViewControllerWidthDelegate, GADBannerViewDelegate { let parent: InlineAdaptiveBannerAdView init(_ parent: InlineAdaptiveBannerAdView) { self.parent = parent } // MARK: BannerViewControllerWidthDelegate methods func bannerViewController(_ bannerViewController: BannerViewController, didUpdate width: CGFloat) { parent.viewWidth = width } // MARK: GADBannerViewDelegate methods func bannerViewDidReceiveAd(_ bannerView: GADBannerView) { print("Did receive ad") print("Ad height: \(bannerView.adSize.size.height)") } func bannerView(_ bannerView: GADBannerView, didFailToReceiveAdWithError error: Error) { print("Did not receive ad: \(error.localizedDescription)") } } } 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) delegate?.bannerViewController(self, didUpdate: view.frame.inset(by: view.safeAreaInsets).size.width) } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { coordinator.animate { _ in // Do nothing } completion: { _ in self.delegate?.bannerViewController(self, didUpdate: self.view.frame.inset(by: self.view.safeAreaInsets).size.width) } } }
If I leave the default .zero
for viewWidth
, the whole process doesn't get through guard viewWidth != .zero else { return }
.
With viewWidth
set to 280, the ad shows, but the size is never updated.
Both viewDidAppear(_:)
and viewWillTransition(to:with:)
never get called, even if the ad gets displayed, so the width is never updated.
As for height, according to Google developer's guide (which I can't link to on this forum):
The height is either zero or maxHeight, depending on which API you're using. The actual height of the ad is made available when it's returned.
So, I can read the ad's height when it is presented, but I still don't know how to really update the view's size.
My brain is fried. Am I missing something very simple and obvious here?