A Swift library to display banner, interstitial, rewarded and native ads from Google AdMob and supported mediation partners.
- Requirements
- Create Accounts
- Mediation
- Installation
- Pre-Usage
- Usage
- App Store release information
- Demos
- License
- Multiple ad unit ids
- Swift package manager support
- iOS 11.4+
- Swift 5.0+
Sign up for an AdMob account and create your required AdUnitIDs for the types of ads you would like to display.
SwiftyAds uses Google`s User Messaging Platform (UMP) SDK to handle user consent if required. This SDK can handle both GDPR requests and also the iOS 14 ATT alert.
This step can be skipped if you would like to disable user consent requests, see Add SwiftyAds.plist
part of pre-usage section below. Otherwise please read the Funding Choices documentation to ensure they are setup up correctly for your requirements.
NOTE: Apple may be rejecting apps that use the UMP SDK to display the iOS 14 ATT alert. As a workaround you may have to tweak the wording of the explainer message or you can manually display the ATT alert before configuring SwiftyAds.
To support mediation networks please read the AdMob documentation
CocoaPods is a dependency manager for Cocoa projects. Simply install the pod by adding the following line to your pod file
pod 'SwiftyAds'
Alternatively you can copy the Sources
folder and its containing files into your project. Install the required dependencies either via Cocoa Pods.
pod 'Google-Mobile-Ads-SDK'
or manually
Download the template plist and add it to your projects main bundle. Than enter your required ad unit ids and set the isTaggedForUnderAgeOfConsent flag.
Mandatory entries:
- isTaggedForUnderAgeOfConsent (Boolean) (GDPR)
Optional entries:
- bannerAdUnitId (String)
- interstitialAdUnitId (String)
- rewardedAdUnitId (String)
- rewardedInterstitialAdUnitId (String)
- nativeAdUnitId (String)
- isTaggedForChildDirectedTreatment (Boolean) (COPPA)
- isUMPDisabled (Boolean)
NOTE: Adding the isUMPDisabled
entry and setting it to true means SwiftyAds will not carry out any consent requests using the User Messaging Platform (UMP) SDK.
In that case you will have to manually support GDPR (EEA) and ATT (Apple) alerts if required.
Link the AppTrackingTransparency framework in Framework, Libraries and Embedded Content
under the general tab, otherwise iOS 14 ATT alerts will not display.
If you are supporting iOS 13 and below you will also have to make it optional in BuildPhases->Link Binary With Libraries
to avoid a crash.
- Add the import statement to your swift file(s) if you installed via CocoaPods
import SwiftyAds
Create a SwiftyAdsRequestBuilder
class, implementing the SwiftyAdsRequestBuilderType
protocol, that SwiftyAds will use to load ads. Some mediation networks such as Vungle may required specific GADRequest extras.
Please check the AdMob mediation documentation.
import SwiftyAds
import GoogleMobileAds
final class SwiftyAdsRequestBuilder {}
extension SwiftyAdsRequestBuilder: SwiftyAdsRequestBuilderType {
func build() -> GADRequest {
GADRequest()
}
}
Create a SwiftyAdsMediationConfigurator
class, implementing the SwiftyAdsMediationConfiguratorType
protocol, to manage updating mediation networks for COPPA/GDPR consent status changes.
Please check the AdMob mediation documentation.
import SwiftyAds
import AppLovinAdapter
final class SwiftyAdsMediationConfigurator {}
extension SwiftyAdsMediationConfigurator: SwiftyAdsMediationConfiguratorType {
func updateCOPPA(isTaggedForChildDirectedTreatment: Bool)
// App Lovin mediation network example
ALPrivacySettings.setIsAgeRestrictedUser(isTaggedForChildDirectedTreatment)
}
func updateGDPR(for consentStatus: SwiftyAdsConsentStatus, isTaggedForUnderAgeOfConsent: Bool) {
// App Lovin mediation network example
ALPrivacySettings.setHasUserConsent(consentStatus == .obtained)
if !ALPrivacySettings.isAgeRestrictedUser() { // skip if already age restricted e.g. enableCOPPA called
ALPrivacySettings.setIsAgeRestrictedUser(isTaggedForUnderAgeOfConsent)
}
}
}
Create a configure method and call it as soon as your app launches e.g. AppDelegate didFinishLaunchingWithOptions
. This will also trigger the initial consent flow if consent has not been disabled. Ads can only be displayed after the completion
handler was called.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let rootViewController = window?.rootViewController {
configureSwiftyAds(from: rootViewController)
}
return true
}
private func configureSwiftyAds(from viewController: UIViewController) {
#if DEBUG
// testDeviceIdentifiers: The test device identifiers used for debugging purposes.
// consentConfiguration: The debug consent configuration:
// 1) .default(geography: UMPDebugGeography),
// 2) .resetOnLaunch(geography: UMPDebugGeography)
// 3) .disabled
let environment: SwiftyAdsEnvironment = .development(testDeviceIdentifiers: [], consentConfiguration: .resetOnLaunch(geography: .EEA))
#else
let environment: SwiftyAdsEnvironment = .production
#endif
SwiftyAds.shared.configure(
from: viewController,
for: environment,
requestBuilder: SwiftyAdsRequestBuilder(),
mediationConfigurator: SwiftyAdsMediationConfigurator(),
consentStatusDidChange: { status in
print("The consent status has changed")
},
completion: { result in
switch result {
case .success(let consentStatus):
print("Configure successful")
// Ads will preload and sohuld be ready for displaying
case .failure(let error):
print("Setup error: \(error)")
}
}
)
}
NOTE: If you do not use the Google UMP SDK to display the iOS 14 App Tracking Transparency (ATT) alert, it is recommended to ask for permission before configuring SwiftyAds.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let rootViewController = window?.rootViewController {
if #available(iOS 14, *) {
ATTrackingManager.requestTrackingAuthorization { status in
self.configureSwiftyAds(from: rootViewController)
}
} else {
configureSwiftyAds(from: rootViewController)
}
}
return true
}
SwiftyAds requires reference to a UIViewController
to present ads. If you are not using SwiftyAds inside a UIViewController
you can do the following to get reference the rootViewController.
AppDelegate
if let viewController = window?.rootViewController {
SwiftyAds.shared.show(...)
}
SKScene
if let viewController = view?.window?.rootViewController {
SwiftyAds.shared.show(...)
}
Create a property in your UIViewController
for the banner to be displayed
class SomeViewController: UIViewController {
private var bannerAd: SwiftyAdsBannerType?
}
Prepare the banner in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
bannerAd = SwiftyAds.shared.makeBannerAd(
in: self,
adUnitIdType: .plist, // set to `.custom("AdUnitId")` to add a different AdUnitId for this particular banner ad
position: .bottom(isUsingSafeArea: true) // banner is pinned to bottom and follows the safe area layout guide
animation: .slide(duration: 1.5),
onOpen: {
print("SwiftyAds banner ad did receive ad and was opened")
},
onClose: {
print("SwiftyAds banner ad did close")
},
onError: { error in
print("SwiftyAds banner ad error \(error)")
},
onWillPresentScreen: {
print("SwiftyAds banner ad was tapped and is about to present screen")
},
onWillDismissScreen: {
print("SwiftyAds banner ad presented screen is about to be dismissed")
},
onDidDismissScreen: {
print("SwiftyAds banner did dismiss presented screen")
}
)
}
and show it in viewDidAppear
. This is to ensure that the view has been layed out correctly and has a valid safe area.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
bannerAd?.show(isLandscape: view.frame.width > view.frame.height)
}
To handle orientation changes, simply call the show method again in viewWillTransition
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { [weak self] _ in
self?.bannerAd?.show(isLandscape: size.width > size.height)
})
}
You can hide the banner by calling the hide
method.
bannerAd?.hide()
You can remove the banner from its superview by calling the remove
method and afterwards nil out the reference.
bannerAd?.remove()
bannerAd = nil
SwiftyAds.shared.showInterstitialAd(
from: self,
afterInterval: 2, // every 2nd time method is called ad will be displayed. Set to nil to always display.
onOpen: {
print("SwiftyAds interstitial ad did open")
},
onClose: {
print("SwiftyAds interstitial ad did close")
},
onError: { error in
print("SwiftyAds interstitial ad error \(error)")
}
)
Rewarded ads may be non-skippable and should only be presented when pressing a dedicated button.
SwiftyAds.shared.showRewardedAd(
from: self,
onOpen: {
print("SwiftyAds rewarded ad did open")
},
onClose: {
print("SwiftyAds rewarded ad did close")
},
onError: { error in
print("SwiftyAds rewarded ad error \(error)")
},
onNotReady: { [weak self] in
guard let self = self else { return }
print("SwiftyAds rewarded ad was not ready")
// If the user presses the rewarded video button and watches a video it might take a few seconds for the next video to reload.
// Use this callback to display an alert incase the video was not ready.
let alertController = UIAlertController(
title: "Sorry",
message: "No video available to watch at the moment.",
preferredStyle: .alert
)
alertController.addAction(UIAlertAction(title: "Ok", style: .cancel))
DispatchQueue.main.async {
self.present(alertController, animated: true)
}
},
onReward: { [weak self] rewardAmount in
print("SwiftyAds rewarded ad did reward user with \(rewardAmount)")
// Provide the user with the reward e.g coins, diamonds etc
}
)
NOTE: AdMob provided a new rewarded video API which lets you preload multiple rewarded videos with different AdUnitIds. While SwiftyAds uses this new API it currently only supports loading 1 rewarded ad at a time.
Rewarded interstitial ads can be presented naturally in your apps flow, similar to interstitial ads, and do not require a dedicated button like regular rewarded ads. Before displaying a rewarded interstitial ad, you must present the user with an intro screen that provides clear reward messaging and an option to skip the ad before it starts.
SwiftyAds.shared.showRewardedInterstitialAd(
from: self,
afterInterval: 2, // every 2nd time method is called ad will be displayed. Set to nil to always display.
onOpen: {
print("SwiftyAds rewarded interstitial ad did open")
},
onClose: {
print("SwiftyAds rewarded interstitial ad did close")
},
onError: { error in
print("SwiftyAds rewarded interstitial ad error \(error)")
},
onReward: { [weak self] rewardAmount in
print("SwiftyAds rewarded interstitial ad did reward user with \(rewardAmount)")
// Provide the user with the reward e.g coins, diamonds etc
}
)
To present a native ad simply call the load method. Once a native ad has been received you can update your custom ad view with the native ad content.
You can set the amount of ads to load (GADMultipleAdsAdLoaderOptions
) via the loaderOptions
parameter. Set to .single
to use the default options.
As per Googles documentation, requests for multiple native ads don't currently work for AdMob ad unit IDs that have been configured for mediation. Publishers using mediation should avoid using the GADMultipleAdsAdLoaderOptions class when making requests. In that case you can also set the loaderOptions
parameter to .single
.
SwiftyAds.shared.loadNativeAd(
from: self,
adUnitIdType: .plist, // set to `.custom("AdUnitId")` to add a different AdUnitId for this particular native ad
loaderOptions: .single, // set to `.multiple(2)` to load multiple ads for example 2
onFinishLoading: {
// Native ad has finished loading and new ads can now be loaded
},
onError: { error in
// Native ad could not load ad due to error
},
onReceive: { nativeAd in
// show native ad (see demo app or google documentation)
}
)
NOTE: While prefetching ads is a great technique, it's important that you don't keep old native ads around forever without displaying them. Any native ad objects that have been held without display for longer than an hour should be discarded and replaced with new ads from a new request.
Use can use the SwiftyAdsError
enum to handle SwiftyAds specific errors.
if let swiftyAdsError = error as? SwiftyAdsError {
switch swiftyAdsError {
case .interstitialAdNotLoaded:
// Ad was not loaded
default:
break
}
}
// Check current consent status
SwiftyAds.shared.consentStatus
// Check if interstitial ad is ready, for example to show an alternative ad
SwiftyAds.shared.isInterstitialAdReady
// Check if rewarded ad is ready, for example to show/hide button
SwiftyAds.shared.isRewardedAdReady
// Check if rewarded interstitial ad is ready, for example to show an alternative ad
SwiftyAds.shared.isRewardedInterstitialAdReady
// Check if child directed treatment is tagged on/off. Nil if not indicated how to be treated. (COPPA)
SwiftyAds.shared.isTaggedForChildDirectedTreatment
// Check if under age of consent is tagged on/off (GDPR)
SwiftyAds.shared.isTaggedForUnderAgeOfConsent
// Check if ads have been disabled
SwiftyAds.shared.isDisabled
Call the disable(_ isDisabled: Bool)
method and banner, interstitial and rewarded interstitial ads will no longer display.
This will not stop regular rewarded ads from displaying as they should have a dedicated button. This way you can remove banner, interstitial and rewarded interstitial ads but still have rewarded ads.
SwiftyAds.shared.disable(true)
For permanent storage you will need to create your own boolean logic and save it in something like NSUserDefaults
, or preferably Keychain
.
Than at app launch, before you call SwiftyAds.shared.configure(...)
, check your saved boolean and disable the ads if required.
if UserDefaults.standard.bool(forKey: "RemovedAdsKey") == true {
SwiftyAds.shared.disable(true)
}
It is required that the user has the option to change their GDPR consent settings, usually via a button in settings.
func consentButtonPressed() {
SwiftyAds.shared.askForConsent(from: self) { result in
switch result {
case .success(let status):
print("Did change consent status")
case .failure(let error):
print("Consent status change error \(error)")
}
}
}
The consent button can be hidden if consent is not required or user is tagged for under age of consent.
// If consent is not required e.g. outside EEA than we do not need to show consent button.
// If inside EEA we need to display the consentButton unless user is tagged for under age of consent
if SwiftyAds.shared.consentStatus == .notRequired {
consentButton.isHidden = true
} else {
consentButton.isHidden = SwiftyAds.shared.isTaggedForUnderAgeOfConsent
}
Make sure to prepare for Apple's App Store data disclosure requirements
Check out the Demos
folder for a UIKit
and SpriteKit
demo project. Make sure to launch the .xcworkspace
when opening the either project.
SwiftyAds is released under the MIT license. See LICENSE for details.