Implementing Admob in SwiftUI
Recently i was working on a project that required me to implement Admob in a SwiftUI app.
I encountered some issues while implementing Admob in SwiftUI, so i decided to make a library Admob-SwiftUI that makes it easy to implement Admob in SwiftUI.
Which problems did i encounter?
- Admob banner not showing up.
- Padding for the Admob banner.
- The new EU consent policy
UMPConsentInformation
. - Unable to update the consent status.
- Ad area unusable if ad is not shown.
Admob Banner not showing up
The Admob banner was not showing up because i had not set the UMPConsentInformation correctly, see the Documentation, and the page in Admob Console.
What did i try to fix the padding?
First i added in every view a padding, (on the bottom) to make sure that the ad would not overlap the view below it.
Using .padding(.bottom, consent.haveConsent ? 50 : 0)
.
This worked okay, but created a white/black bar where the ad would be shown, and i needed to add it in every view what i displayed on screen, this increases the chance of forgetting to add it.
Using a padding on each view in the TabView. (this works okay, but creates a white/black bar where the ad will be shown) .padding(.bottom, consent.haveConsent ? 50 : 0)
.
This is also not the solution i wanted.
I searched if there is a way to add some extra padding to the contents of the views in the TabView
, after a lot of searching i found contentMargins(_:for:) and this looked promising, so i tried to add that to my app.
But unfortunalet there was a catch, it only works on iOS 17+ and my app supports iOS 16 and up.
I tried to use it with a if #available(iOS 17, *)
, but the issue is, you can't use if #available(iOS 17, *)
in a View
in SwiftUI.
So i needed to come up with a different solution...
What about a ViewModifier
? no that would not work...
What about a ViewBuilder
?
This can work, let's try it out.
extension View {
@ViewBuilder
func addAdPadding(height: CGFloat) -> some View {
if #available(iOS 17.0, *) {
// If iOS 17+ use contentMargins
// This is the nices solution,
// but it only works on iOS 17+
self.contentMargins(
.bottom,
height + 10,
for: .scrollContent
)
} else {
// Fallback on earlier versions
self.padding(
.bottom,
height
)
}
}
}
We have our final solution, this works on iOS 16 and up, and it's easy to use.
The new EU consent policy UMPConsentInformation
You create a view with the class name AdConsentView
and then you can call the updateConsent
function to update the consent status.
I call it like this:
struct AdConsentView: View {
@EnvironmentObject
private var adHelper: AdHelper
var body: some View {
VStack { }
.background(formViewControllerRepresentableView)
.onAppear {
guard !hasViewAppeared else { return }
hasViewAppeared = true
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.askConsent()
}
}
}
}
Full code: https://github.com/0xWDG/Admob-SwiftUI/blob/main/Sources/Admob-SwiftUI/AdConsentView.swift
Updating the consent status
The code for updating the consent is:
func updateConsent() {
GoogleMobileAdsConsentManager.shared.presentPrivacyOptionsForm(
from: formViewControllerRepresentable.viewController
) { (formError) in
guard let formError else { return }
// Handle the error
}
}
The problem with AdConsentView
is that your SwiftUI view is not re-usable for calling only the updateConsent
function, so you have to create formViewControllerRepresentable.viewController
and the updateConsent
function in multiple files, in my case this did not work and i was unable to update the consent of the user.
My solution was to point to the updateConsent()
function, to that i can call the updateConsent
function from the AdHelper
class.
struct AdConsentView: View {
func updateConsent() {
GoogleMobileAdsConsentManager.shared.presentPrivacyOptionsForm(
from: formViewControllerRepresentable.viewController
) { (formError) in
guard let formError else { return }
// Handle the error
}
}
@MainActor
func askConsent() {
// Point to the updateConsent function
adHelper.updateConsent = updateConsent
// initial consent question
}
}
Ad area unusable if ad is not shown
If the ad is not shown, the view (zstack) will be overlap the view below it, making the view below it unusable, this is intentional if the ad is shown, but if the ad is not shown, the view below it should be usable.
the way to fix this is to set the opacity of the ad to 0 if the ad is not shown. like the snippet below.
.opacity(adHelper.showingAd ? 1 : 0)
Reset the consent status
You can use UMPConsentInformation.sharedInstance.reset()
to reset the consent status.
Since the library is already handling the consent status, you can use
adHelper.resetConsent()
(which is shorter and easier to remember)
Before the library
struct ContentView: View {
@ObservedObject var adHelper = AdHelper(
adUnitId: "ca-app-pub-5555094756467155/7714412839"
)
ZStack {
TabView {
Text("First View")
.tabItem {
Image(systemName: "1.square.fill")
Text("First")
}
.padding(.bottom, adHelper.adHeight)
UpdateConsent()
.tabItem {
Image(systemName: "2.square.fill")
Text("Second")
}
.padding(.bottom, adHelper.adHeight)
}
AdConsentView()
.environmentObject(adHelper)
if adHelper.haveConsent {
VStack {
Spacer()
BannerView(adUnitID: adHelper.adUnitId)
.background(.clear)
.frame(width: 320, height: adHelper.adHeight)
.padding(.bottom, adHelper.adHeight + 1)
.opacity(adHelper.showingAd ? 1 : 0)
.environmentObject(adHelper)
}
}
}
.environmentObject(adHelper)
}
With the library
struct ContentView: View {
@ObservedObject var adHelper = AdHelper(
adUnitId: "ca-app-pub-5555094756467155/7714412839"
)
AdView {
TabView {
Text("First View")
.tabItem {
Image(systemName: "1.square.fill")
Text("First")
}
UpdateConsent()
.tabItem {
Image(systemName: "2.square.fill")
Text("Second")
}
}
}
.environmentObject(adHelper)
}
Update/Reset the consent status
struct UpdateConsent: View {
@EnvironmentObject
private var adHelper: AdHelper
var body: some View {
ScrollView {
VStack {
Button("Reset consent", role: .destructive) {
adHelper.resetConsent()
}
Button("Update Consent") {
adHelper.updateConsent()
}
}
}
}
}
The library is available on Github: https://github.com/0xWDG/Admob-SwiftUI.
Read more
- ExpressibleByStringLiteral URL • 2 minutes reading time.
- If case let • 3 minutes reading time.
- Default values for UserDefaults • 2 minutes reading time.
Share
Share Mastodon Twitter LinkedIn Facebook