Skip to content
Menu
Mobile

Adding AdMob Ads to an iOS App

Swift Package Manager, Info.plist keys, SKAdNetwork, ATT, UMP consent and Swift code for banner, interstitial and rewarded ads, with test ads.

12 min read · Updated

To add AdMob ads to an iOS app, add the Google Mobile Ads SDK with Swift Package Manager, put your AdMob App ID in Info.plist under GADApplicationIdentifier, call MobileAds.shared.start() once, and load ads with Google's test ad unit IDs until you are ready to ship. For European users you also need a consent message, and for tracking you need Apple's App Tracking Transparency prompt.

Setup takes about an hour if nothing goes wrong. The part that costs people days is the iOS-specific paperwork: the property list keys, the SKAdNetwork list, the ATT string and the order in which consent and tracking prompts appear. This guide covers each of them, then gives short Swift examples for a banner, an interstitial and a rewarded ad, in UIKit with a SwiftUI note where the approach differs.

All class names, keys and test IDs here come from Google's current iOS documentation. The SDK renamed most of its types a few major versions ago (the old GAD prefix is gone from the Swift API), so older tutorials will not compile as written. Check the iOS quick start for anything that has changed since this was written.

What you need first

An AdMob account, with your iOS app registered in it. Registering gives you an App ID in the form ca-app-pub-1234567890123456~1234567890, with a tilde. Each ad placement then gets an ad unit ID with a slash instead: ca-app-pub-1234567890123456/1234567890. The App ID goes in Info.plist. Ad unit IDs go in code. Swapping them is the most common mistake on both platforms.

The current quick start asks for Xcode 16.0 or higher and an iOS deployment target of 13.0 or higher. The iOS release notes list version 13.11.0 as the latest at the time of writing, and the 13.0.0 release is the one that set iOS 13 as the floor. If your app still targets iOS 12, you will need to raise the target or stay on an older SDK, and an older SDK means missing newer ad demand.

Set up payments early too. The identity and address checks behind your first payout take time, and our AdMob payment guide lists the thresholds and what each check involves.

One last point on money before the code. iOS audiences usually pay better than Android ones. We model iOS at 1.3 times the Android baseline, so a Tier 1 AdMob interstitial of about $8.00 on Android becomes roughly $10.40, which is a modelled estimate, not a promise. The catch is that iOS income depends heavily on tracking permission, which is why the ATT section below matters more than it looks. Our guide on App Tracking Transparency and iOS ad earnings goes into the mechanics.

Setting up the project

Step 1: install the SDK

Google recommends Swift Package Manager. In Xcode, choose File > Add Package Dependencies, paste this repository URL, and pick "Up to Next Major Version" for a new project:

https://github.com/googleads/swift-package-manager-google-mobile-ads.git

Add the package product to your app target, then import GoogleMobileAds wherever you use it.

CocoaPods still works. Add pod 'Google-Mobile-Ads-SDK' to the target in your Podfile and run pod install --repo-update. There is one catch worth knowing about. Because the SDK supports iOS 13, Xcode 27 builds fail under CocoaPods, since that Xcode requires a 15.0 minimum deployment target. Google's quick start gives a Podfile post_install hook that sets IPHONEOS_DEPLOYMENT_TARGET to 15.0 for the pods; copy it from the page rather than writing your own. If you hit a strange deployment-target error after updating Xcode, this is the likely cause. Swift Package Manager avoids the problem.

A manual install is possible too. You add GoogleMobileAds.xcframework and UserMessagingPlatform.xcframework, set both to Embed & Sign, add /usr/lib/swift to Runpath Search Paths and -ObjC to Other Linker Flags. Nobody enjoys doing that twice, so use a package manager if you can.

Step 2: edit Info.plist

Three keys matter. The first is the App ID:

<key>GADApplicationIdentifier</key>
<string>ca-app-pub-3940256099942544~1458002511</string>

That string is Google's sample iOS App ID. It is for development only. Replace it with your own before release. If the key is missing or wrong, expect the app to crash on launch, which at least means you find out fast.

The second is SKAdNetworkItems, an array of dictionaries, each holding one SKAdNetworkIdentifier. SKAdNetwork is Apple's install attribution system. Advertisers use it to learn which ad led to an install without learning who the user is. For the buyers in AdMob's auctions to get credit for installs they drive through your app, their identifiers have to appear in your plist. Google's list is long, roughly fifty entries, and it changes. The first item must be cstr6suwn9.skadnetwork, which is Google's own. Copy the whole current list from the quick start page instead of typing it from memory, because a short list quietly reduces demand. If you add mediation partners later, each one publishes extra identifiers of its own.

<key>SKAdNetworkItems</key>
<array>
  <dict>
    <key>SKAdNetworkIdentifier</key>
    <string>cstr6suwn9.skadnetwork</string>
  </dict>
  <!-- copy the remaining entries from Google's quick start -->
</array>

The third is NSUserTrackingUsageDescription, covered in the ATT section. You only need it if you show Apple's tracking prompt, but nearly every ad-funded app does.

Step 3: start the SDK

Call the start method once, as early as you reasonably can. The Swift form is short:

import GoogleMobileAds

MobileAds.shared.start()

In a UIKit app that goes in application(_:didFinishLaunchingWithOptions:). In a SwiftUI app you can put it in the App initializer or an app delegate adapter. You can pass a completion handler, which runs when the SDK and any mediation adapters have finished starting or after a 30-second timeout. If you use mediation, wait for it before loading ads. If you only use AdMob, you can begin loading straight away.

Be careful about timing, though. If you ask for consent or tracking permission, start the SDK after those steps finish, not before. The sections below give the order.

Step 4: test ads and test devices

Develop against Google's demo units, never against your own live ones. Clicking your own ads is a fast route to an invalid-activity flag, and Google warns about it in its test ads documentation. These are the iOS demo ad unit IDs:

FormatiOS demo ad unit ID
Anchored adaptive bannerca-app-pub-3940256099942544/2435281174
Interstitialca-app-pub-3940256099942544/4411468910
Rewardedca-app-pub-3940256099942544/1712485313
Rewarded interstitialca-app-pub-3940256099942544/6978759866
App openca-app-pub-3940256099942544/5575463023
Nativeca-app-pub-3940256099942544/3986624511

Simulators count as test devices automatically. A real iPhone needs registering. Run the app and make a request, then read the Xcode console for a message beginning "To get test ads on this device, set:" followed by your device identifier. Add it:

MobileAds.shared.requestConfiguration.testDeviceIdentifiers = ["YOUR_DEVICE_ID"]

Google ads in test mode carry a "Test mode" label. Ads from mediation partners do not, so turn on test mode in each partner's dashboard and do not click anything you cannot identify. A new test device can take up to 24 hours to begin receiving test ads, although 15 minutes is typical. Remove this line before you submit to the App Store.

Test impressions do not show up in your reports and do not earn anything. They also tell you nothing about eCPM, so use the AdMob revenue calculator for planning instead.

Step 5: a banner

Banners pay least. In our benchmark estimates a Tier 1 AdMob banner is about $0.90 eCPM at the Android baseline, compared with roughly $8.00 for an interstitial. See the banner format page for the comparison. They suit screens where a full-screen ad would be intrusive, like a list or a settings page.

In UIKit, create a BannerView, set the ad unit and an adaptive size, constrain it, and load:

let bannerView = BannerView()
bannerView.delegate = self
bannerView.translatesAutoresizingMaskIntoConstraints = false
bannerView.adUnitID = "ca-app-pub-3940256099942544/2435281174"
bannerView.adSize = largeAnchoredAdaptiveBanner(width: view.frame.width)
view.addSubview(bannerView)
NSLayoutConstraint.activate([
    bannerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
    bannerView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
bannerView.load(Request())

Google's own example hard-codes a width; derive yours from the real container, and request a new banner when rotation changes it, which is done in viewWillTransition(to:with:). The size must be no larger than the screen.

The delegate protocol, BannerViewDelegate, has optional methods including bannerViewDidReceiveAd(_:) and bannerView(_:didFailToReceiveAdWithError:). Implement only what you need. If refresh is on for the ad unit in the AdMob console, the SDK refreshes the banner itself, so do not add a timer.

SwiftUI has no native banner, so you wrap one. Google's documentation does it with a UIViewRepresentable whose makeUIView builds the view with BannerView(adSize: adSize), sets the ad unit ID, calls load(Request()) and assigns a coordinator as the delegate. That is fewer than twenty lines, and you can copy it from the banner guide.

Step 6: an interstitial

Interstitials fill the screen at a natural break, such as between levels or after finishing a task. Load early, show later. Each ad object is single use and expires after about an hour, so the cycle is load, hold, show, load the next one.

final class InterstitialManager: NSObject, FullScreenContentDelegate {
    private var interstitial: InterstitialAd?

    func load() async {
        do {
            interstitial = try await InterstitialAd.load(
                with: "ca-app-pub-3940256099942544/4411468910", request: Request())
            interstitial?.fullScreenContentDelegate = self
        } catch {
            interstitial = nil
        }
    }

    func show(from viewController: UIViewController) {
        guard let ad = interstitial else { Task { await load() }; return }
        ad.present(from: viewController)
    }

    func adDidDismissFullScreenContent(_ ad: FullScreenPresentingAd) {
        interstitial = nil
        Task { await load() }
    }
}

Two details deserve attention. First, clear the reference in the dismiss callback and in ad(_:didFailToPresentFullScreenContentWithError:) as well, which this short example leaves out for space. Showing a used ad again fails. Second, do not present the ad from inside the load completion. The user did not ask for it at that moment, and Google's guide tells you to check that the ad can be presented before showing it.

In SwiftUI, Google's example calls present(from: nil) from a view model, and the SDK finds the top view controller itself. If there is no ad ready when the break arrives, carry on without one. Never make a player wait.

How often to show them is your call. A prompt after every action will cost you more in retention than it earns, and our rewarded versus interstitial comparison covers pacing. The interstitial format page has the pricing side.

Step 7: a rewarded ad

Rewarded ads pay the most in our tables, because the person chooses to watch in return for something. Loading mirrors the interstitial, with RewardedAd.load(with:request:) and the demo unit ca-app-pub-3940256099942544/1712485313. Presenting adds a closure that runs when the reward is earned:

rewardedAd.present(from: viewController) {
    let reward = rewardedAd.adReward
    grantCoins(reward.amount.intValue)
}

reward.amount and reward.type come from what you set on the ad unit in AdMob. Give the reward inside this closure, not when the ad closes. For Google-served ads it fires before the dismiss callback, but with mediation the order depends on the partner, so do not rely on it.

The same FullScreenContentDelegate methods apply: record impressions, and clear the reference and preload the next ad after dismissal. If the reward carries real value, such as paid currency, use server-side verification. Create a ServerSideVerificationOptions, set customRewardText on it, and assign it to the ad's serverSideVerificationOptions before presenting. Your server then confirms the callback AdMob sends. Reward logic that lives only on the phone is easy to tamper with on a jailbroken device.

Our rewarded format page shows how the estimates for this format change by region.

Two separate prompts can apply, and they are easy to confuse.

The first is the privacy message required by laws in the EEA, UK and Switzerland. Google's User Messaging Platform (UMP) SDK shows a message you create in your AdMob account under Privacy and messaging. Add the UMP package alongside the ads SDK; Google publishes its own Swift package at https://github.com/googleads/swift-package-manager-google-user-messaging-platform.git, and CocoaPods users add GoogleUserMessagingPlatform. Our consent messages guide explains which message types to turn on.

On every launch, request a consent update, present the form if one is required, and only then check whether ads may be requested:

ConsentInformation.shared.requestConsentInfoUpdate(with: RequestParameters()) { error in
    Task { @MainActor in
        try? await ConsentForm.loadAndPresentIfRequired(from: viewController)
        if ConsentInformation.shared.canRequestAds {
            MobileAds.shared.start()
        }
    }
}

canRequestAds is always false until the update call has run, so never read it earlier. You also have to offer a way to change the choice later. Check whether privacyOptionsRequirementStatus is .required, and if it is, show a privacy settings button that calls ConsentForm.presentPrivacyOptionsForm(from:).

The second prompt is Apple's App Tracking Transparency alert. Add NSUserTrackingUsageDescription to the plist, with a plain sentence explaining why you ask. Google's sample wording is "This identifier will be used to deliver personalized ads to you." Then call Apple's ATTrackingManager.requestTrackingAuthorization, from the AppTrackingTransparency framework, once the app is active, and wait for it to finish before loading ads so the SDK can use the identifier if the user agrees. The iOS system alert only ever appears once per install. To see it again on a test device, uninstall and reinstall the app.

If you use the UMP SDK's IDFA message, it explains the request first and then triggers Apple's alert; set it up in AdMob first. A user who says no can still be shown ads, but the SDK does not send the advertising identifier with the request. Apple's own reference for the framework is on its App Tracking Transparency page.

If the app is aimed at children, different rules apply to both consent and ad requests. Read our guide to ads in kids apps before integrating.

Errors you are likely to see

The app crashes at launch with an App ID message. GADApplicationIdentifier is missing, or holds an ad unit ID. Check for the tilde.

No fill. The request worked, but nobody had an ad. For a new ad unit that is normal for a while. For a demo unit it should not happen, so recheck the ID. Persistent no-fill in production is a fill rate problem, often caused by geography, floors or a missing app-ads.txt file on the App Store.

Invalid request. The ad unit ID is wrong or belongs to a different format. An interstitial ID will not work for a rewarded request.

Code that will not compile. Most Swift snippets older than the last big rename use GADBannerView, GADRequest and GADInterstitialAd. In the current SDK these are BannerView, Request and InterstitialAd. Google's page on ad load errors is the place to translate error codes.

Ads work in the simulator but not on a device. Simulators are test devices automatically, real phones are not. Register the device identifier from the console message, then allow some time for it to take effect.

A preloaded ad stops showing. Loaded ads expire after an hour. If your app sits in the background for a long time, reload before the next break.

Ad inspector shows each request and response on a registered test device, including mediation results per network. It needs a version of the SDK from 7.68.0 on, which any current install exceeds, and it uses extra memory while enabled, so leave it out of release builds. Google documents it on its ad inspector page.

Before you submit

Run through this list:

  • Replace the sample App ID in Info.plist and every demo ad unit ID in code.
  • Delete the test device line.
  • Confirm the full SKAdNetworkItems list is present and the ATT description is written in plain language.
  • Test the consent form with a device region set to somewhere in the EEA.
  • Publish your app-ads.txt, covered in our guide for iOS apps.
  • Answer the App Store privacy questions honestly, since ad SDKs collect data and Apple checks.

Do not click your own live ads, and do not ask friends to. It reads as invalid traffic, and the penalty can be a limited or disabled account under the AdMob policies. Our pages on invalid traffic and why ad accounts get limited explain how that goes wrong.

Common questions

Do I have to show the ATT prompt?

Only if you want access to the advertising identifier. Without it, ads still serve but are usually worth less. Many apps ask, after giving the user a reason first. Don't show it on the very first screen with no context.

Swift Package Manager or CocoaPods?

Google lists Swift Package Manager as the recommended route. CocoaPods is fine, but note the Xcode 27 deployment-target problem described above.

Does SwiftUI need different code?

Mostly the same SDK calls. Banners need a UIViewRepresentable wrapper, and full-screen ads can be presented from a view model with a nil view controller, as in Google's examples.

What about Android?

The flow is similar, with different packages and manifest keys. We cover it in adding AdMob to an Android app, and Flutter apps have their own walkthrough.

What to do next

Get one format working with test IDs, on a real device, before adding another. A banner on a main screen plus a rewarded ad behind a clearly labeled button is a sensible first release.

Then put numbers on it. Open the AdMob revenue calculator with your expected daily users, and compare the puzzle app estimates with your own category. After launch, learn to read the console with How to read your AdMob report. If eCPM disappoints, work through why your eCPM is low in order. When one network stops being enough, AdMob mediation is the next step.