SwiftUI Case Study: Presenting from View Controllers
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:
In UIKit, ads are typically implemented in a
We created an implementation of the
To present the full screen ads in our sample app, we leveraged action events in SwiftUI.
And our
And voila!
Instead of creating a
We decided against this option for the following reasons:
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.
present(fromRootViewController rootViewController: UIViewController)
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
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)
}
}
Button("Watch an ad!") {
coordinator.presentAd(from: adViewControllerRepresentable.viewController)
}
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)
}
}
An alternative option
UIViewControllerRepresentable, there was always the option to query the
rootViewController property from
UIWindow.
UIApplication.shared.windows.first?.rootViewController
rootViewController is nil.
windows array will have multiple elements and therefore, makes querying the “first” window object unreliable.
windows on the
UIApplication object is deprecated in iOS 15 and
UIWindowScene now holds the reference to this property.
Conclusion