Wesley de Groot's Blog
Implementing Admob in SwiftUI

Back

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

Share


Share Mastodon Twitter LinkedIn Facebook
x-twitter mastodon github linkedin discord threads instagram whatsapp bluesky square-rss sitemap