Implementing Admob in SwiftUI
Tags: swiftadmob
Published: 2024-02-27 16:08:21

---
title: Implementing Admob in SwiftUI
published: true
date: 2024-02-11 17:35:01 UTC
tags: SwiftAdmob
canonical_url: https://wesleydegroot.nl/blog/post/Admob-in-SwiftUI
---

<p>Recently i was working on a <a href="https://wesleydegroot.nl/apps/ddv">project</a> that required me to implement Admob in a SwiftUI app.</p> <p>I encountered some issues while implementing Admob in SwiftUI, so i decided to make a library <a href="https://github.com/0xWDG/Admob-SwiftUI">Admob-SwiftUI</a> that makes it easy to implement Admob in SwiftUI.</p> <h3>Which problems did i encounter?</h3> <ul> <li>Admob banner not showing up.</li> <li>Padding for the Admob banner.</li> <li>The new EU consent policy <code>UMPConsentInformation</code>.</li> <li>Unable to update the consent status.</li> <li>Ad area unusable if ad is not shown.</li> </ul> <h3>Admob Banner not showing up</h3> <p>The Admob banner was not showing up because i had not set the <a href="https://developers.google.com/admob/ios/privacy/api/reference/Classes/UMPConsentInformation">UMPConsentInformation</a> correctly, see the <a href="https://support.google.com/admob/answer/13554116">Documentation</a>, and the page in <a href="https://apps.admob.com/v2/privacymessaging/gdpr">Admob Console</a>.</p> <h4>What did i try to fix the padding?</h4> <p>First i added in every view a padding, (on the bottom) to make sure that the ad would not overlap the view below it.<br> Using <code>.padding(.bottom, consent.haveConsent ? 50 : 0)</code>.<br> 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.</p> <p>Using a padding on each view in the TabView. (this works okay, but creates a white/black bar where the ad will be shown) <code>.padding(.bottom, consent.haveConsent ? 50 : 0)</code>.<br> This is also not the solution i wanted.</p> <p>I searched if there is a way to add some extra padding to the contents of the views in the <code>TabView</code>, after a lot of searching i found <a href="https://developer.apple.com/documentation/swiftui/view/contentmargins(\_:for:)?changes=\_4">contentMargins(\_:for:)</a> and this looked promising, so i tried to add that to my app.<br> But unfortunalet there was a catch, it only works on iOS 17+ and my app supports iOS 16 and up.<br> I tried to use it with a <code>if #available(iOS 17, \*)</code>, but the issue is, you can't use <code>if #available(iOS 17, \*)</code> in a <code>View</code> in SwiftUI.</p> <p>So i needed to come up with a different solution...</p> <p>What about a <code>ViewModifier</code>? no that would not work...</p> <p>What about a <code>ViewBuilder</code>?<br> This can work, let's try it out.</p> <pre><code class="language-swift">extension View { @ViewBuilder func addAdPadding(height: CGFloat) -&gt; 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 ) } } }</code></pre> <p>We have our final solution, this works on iOS 16 and up, and it's easy to use.</p> <h3>The new EU consent policy <code>UMPConsentInformation</code></h3> <p>You create a view with the class name <code>AdConsentView</code> and then you can call the <code>updateConsent</code> function to update the consent status.</p> <p>I call it like this:</p> <pre><code class="language-swift">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() } } } }</code></pre> <p>Full code: <a href="https://github.com/0xWDG/Admob-SwiftUI/blob/main/Sources/Admob-SwiftUI/AdConsentView.swift">https://github.com/0xWDG/Admob-SwiftUI/blob/main/Sources/Admob-SwiftUI/AdConsentView.swift</a></p> <h3>Updating the consent status</h3> <p>The code for updating the consent is:</p> <pre><code class="language-swift">func updateConsent() { GoogleMobileAdsConsentManager.shared.presentPrivacyOptionsForm( from: formViewControllerRepresentable.viewController ) { (formError) in guard let formError else { return } // Handle the error } }</code></pre> <p>The problem with <code>AdConsentView</code> is that your SwiftUI view is not re-usable for calling only the <code>updateConsent</code> function, so you have to create <code>formViewControllerRepresentable.viewController</code> and the <code>updateConsent</code> function in multiple files, in my case this did not work and i was unable to update the consent of the user.</p> <p>My solution was to point to the <code>updateConsent()</code> function, to that i can call the <code>updateConsent</code> function from the <code>AdHelper</code> class.</p> <pre><code class="language-swift">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 } }</code></pre> <h3>Ad area unusable if ad is not shown</h3> <p>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.</p> <p>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.</p> <p><code>.opacity(adHelper.showingAd ? 1 : 0)</code></p> <h3>Reset the consent status</h3> <p>You can use <code>UMPConsentInformation.sharedInstance.reset()</code> to reset the consent status.<br> Since the library is already handling the consent status, you can use<br> <code>adHelper.resetConsent()</code> (which is shorter and easier to remember)</p> <h2>Before the library</h2> <pre><code class="language-swift">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) }</code></pre> <h2>With the library</h2> <pre><code class="language-swift">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) }</code></pre> <p><strong>Update/Reset the consent status</strong></p> <pre><code class="language-swift">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() } } } } }</code></pre> <p>The library is available on Github: <a href="https://github.com/0xWDG/Admob-SwiftUI">https://github.com/0xWDG/Admob-SwiftUI</a>.</p>