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?