We are happy to announce the release of an iOS sample application that demonstrates how to integrate the Google Mobile Ads SDK into a SwiftUI-based app. This post covers how we implemented full screen ad formats (interstitial, rewarded, rewarded interstitial) in SwiftUI.
The Google Mobile Ads SDK relies heavily on the UIKit Framework, depending on UIView or UIViewController for each ad format. For example, the SDK currently presents full screen ads using the following method:
present(fromRootViewController rootViewController: UIViewController)
In UIKit, ads are typically implemented in a UIViewController
, so it is rather trivial to pass in a rootViewController
value by simply invoking self
. SwiftUI requires us to diverge from this approach, however, because UIViewController
cannot be directly referenced in SwiftUI. Since we can’t just pass in self
as the root view controller, we needed to achieve a similar result using a SwiftUI-native approach.
Our solution
We created an implementation of the UIViewControllerRepresentable
protocol with a UIViewController
property. Its one job is to provide access to the UIViewController
reference in SwiftUI.
private struct AdViewControllerRepresentable: UIViewControllerRepresentable { let viewController = UIViewController() func makeUIViewController(context: Context) -> some UIViewController { return viewController } func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {} }
AdViewControllerRepresentable
needs to be included as part of the view hierarchy even though it holds no significance to the content on screen. This is because canPresent(fromRootViewController:)
requires the presenting view controller’s window
value to not be nil.
private let adViewControllerRepresentable = AdViewControllerRepresentable() var body: some View { Text("hello, friend.") .font(.largeTitle) // Add the adViewControllerRepresentable to the background so it // does not influence the placement of other views in the view hierarchy. .background { adViewControllerRepresentable .frame(width: .zero, height: .zero) } }
To present the full screen ads in our sample app, we leveraged action events in SwiftUI.
Button("Watch an ad!") { coordinator.presentAd(from: adViewControllerRepresentable.viewController) }
And our AdCoordinator
class does the honor of presenting it from our view controller.
private class AdCoordinator: NSObject { private var ad: GADInterstitialAd? ... func presentAd(from viewController: UIViewController) { guard let ad = ad else { return print("Ad wasn't ready") } ad.present(fromRootViewController: viewController) } }
And voila!
An alternative option
Instead of creating a UIViewControllerRepresentable
, there was always the option to query the rootViewController
property from UIWindow
.
UIApplication.shared.windows.first?.rootViewController
We decided against this option for the following reasons:
- There is the inherent nullability risk to querying an optional array index.
- The default value of
rootViewController
is nil. - If your app utilizes more than one window, the
windows
array will have multiple elements and therefore, makes querying the “first” window object unreliable. windows
on theUIApplication
object is deprecated in iOS 15 andUIWindowScene
now holds the reference to this property.
Conclusion
We know there is more than one way to cook an egg when it comes to writing code in SwiftUI. For our use case, we chose the most low-code friendly option. If you have any questions, reach out to our developer forum.