Ads#
Introduction#
The Storyteller SDK supports displaying ads that can be created in the Storyteller CMS (First Party Ads), as well as Ads from Google Ad Manager via an SDK extension developed by Storyteller and Ads from other sources via custom implementation provided by the integrator.
Which source of ads is used can be configured on your behalf by a member of the Storyteller Delivery Team.
Storyteller First Party Ads#
If your tenant is configured to use Storyteller First Party Ads, which can be managed in the Storyteller CMS, then no changes to the Storyteller integration code are necessary. The Ads code is managed entirely within the Storyteller SDK.
Storyteller GAM SDK#
To use Ads from Google Ad Manager in Storyteller, first reach out to your Storyteller contact and they will assist you with setting up Google Ad Manager to traffic ads to Storyteller.
You will then need to use the Storyteller Google Ad Manager SDK extension to fetch the ads from Google Ad Manager.
To use this extension, first install it using Swift Package Manager or Cocoapods.
For Swift Package Manager, it is available on Github here:
https://github.com/getstoryteller/storyteller-gam-module-swift
For Cocoapods, first make sure to specify the sources for Cocoapods:
source 'https://github.com/getstoryteller/storyteller-sdk-ios-podspec.git'
source 'https://cdn.cocoapods.org/'
The StorytellerGAMIntegration is available by importing this pod:
pod 'StorytellerGAMIntegration'
Basic Setup#
Make sure to import the same version of StorytellerSDK and StorytellerGAMIntegration.
Now initialize the extension as follows:
import StorytellerSDK
import StorytellerGAMIntegration
let configuration = StorytellerGAMModuleConfiguration(
adUnit: { requestInfo in
return "YOUR_AD_UNIT_ID"
}
)
You will need to supply the following parameter:
| Parameter Name | Description |
|---|---|
adUnit |
A closure that returns the ID of the Ad unit in Google Ad Manager that will be used to serve the Storyteller Ads for the specific Ad request. This can be used for custom Ad units depending on the request context. |
bottomBannerAdUnit |
Optional closure that returns the Ad unit ID used specifically for the Clips bottom banner placement. Leave this nil if you don't plan to serve Clips bottom banner Ads. |
Then pass the newly created instance of the extension to the modules property on the Storyteller instance:
Storyteller.shared.modules = [StorytellerGAMModule(configuration: configuration)]
Our Showcase app uses this module to integrate ads - the relevant code is available here
Setup with Dynamic Ad Unit Changes#
Example for dynamic Ad unit changes when you want to use different Ad units for Stories and Clips:
import StorytellerSDK
import StorytellerGAMIntegration
let configuration = StorytellerGAMModuleConfiguration(
adUnit: { requestInfo in
switch requestInfo {
case .stories:
return "/33813572/storyteller/stories"
case .clips:
return "/33813572/storyteller/clips"
}
}
)
Setup with Additional Parameters#
You can also supply optional parameters customNativeTemplateIds and customKvps if needed:
import StorytellerSDK
import StorytellerGAMIntegration
let configuration = StorytellerGAMModuleConfiguration(
adUnit: { requestInfo in
switch requestInfo {
case .stories:
return "/33813572/storyteller/stories"
case .clips:
return "/33813572/storyteller/clips"
}
},
customNativeTemplateIds: StorytellerGAMModuleConfiguration.CustomNativeTemplateIds(
stories: "YOUR_STORIES_TEMPLATE_ID",
clips: "YOUR_CLIPS_TEMPLATE_ID"
),
customKvps: {
["appmode": "prod"]
}
)
| Parameter Name | Description |
|---|---|
customNativeTemplateIds |
If you have worked with the Storyteller Delivery team to setup Custom Native Ads, you will need to supply their IDs here. If you are only using Stories (but not Clips) it is only necessary to supply one property of this struct. |
customKvps |
A closure that is called each time we request a new ad. The Storyteller GAM SDK passes a default set of KVPs to GAM to allow targeting based on the content of the Stories/Clips the user is viewing. If you have any additional parameters that you need to be able to target by, these should be passed here. Note that the SDK will not inherit any KVPs being set in the rest of your app. |
Default KVPs#
The Storyteller GAM SDK automatically sends a set of key-value pairs (KVPs) to Google Ad Manager to enable content-based targeting. These KVPs are only sent when ad tracking is enabled (Storyteller.shared.eventTrackingOptions.enableAdTracking = true).
For Stories:
| KVP Key | Description |
|---|---|
stCategories |
Categories associated with the current story |
stCurrentCategory |
Categories of the list containing the story |
stPlacement |
The placement identifier of the story |
stApiKey |
The current Storyteller API key |
stAdIndex |
The order of the ad within the story |
For Clips:
| KVP Key | Description |
|---|---|
stCollection |
The identifier of the clip collection |
stClipCategories |
Categories associated with the current clip |
stNextClipCategories |
Categories of the next clip (if available) |
stApiKey |
The current Storyteller API key |
stAdIndex |
The order of the ad within the clip collection |
Storyteller AdMob SDK#
AdMob support uses the same StorytellerGAMIntegration artifact but a different module entry point. Only one ads integration module can be used at a time.
Due to AdMob limitations, banner Ads cannot be served from the same Ad unit as native Ads. adUnit is always used for native Ads, while bannerAdUnit is used for fullscreen banner Ads.
- Default (
enableBannerAdPriority = false): the module tries to load a native Ad fromadUnitfirst. If native loading fails andbannerAdUnitis configured, it falls back to a fullscreen banner Ad frombannerAdUnit. - Banner priority enabled (
enableBannerAdPriority = true): ifbannerAdUnitis configured, the module tries to load a fullscreen banner Ad first. If banner loading fails, it falls back to a native Ad fromadUnit.
enableBannerAdPriority only affects this fullscreen fallback order. It has no effect when bannerAdUnit is nil, and it does not change Clips bottom banner behavior configured through bottomBannerAdUnit.
Basic Setup#
import StorytellerSDK
import StorytellerGAMIntegration
let configuration = StorytellerAdMobModuleConfiguration(
adUnit: { requestInfo in
return "YOUR_NATIVE_AD_UNIT_ID"
},
bannerAdUnit: { requestInfo in
return "YOUR_BANNER_AD_UNIT_ID"
},
bottomBannerAdUnit: { requestInfo in
return "YOUR_BOTTOM_BANNER_AD_UNIT_ID"
}
)
Storyteller.shared.modules = [StorytellerAdMobModule(configuration: configuration)]
| Parameter Name | Description |
|---|---|
adUnit |
Required closure that returns the native Ad unit ID. |
bannerAdUnit |
Optional closure that returns a fullscreen banner Ad unit ID. If supplied, the module can use it as the banner fallback path or the banner-first path when enableBannerAdPriority is enabled. |
bottomBannerAdUnit |
Optional closure that returns the Ad unit ID used specifically for the Clips bottom banner placement. Leave this nil if you don't plan to serve Clips bottom banner Ads. |
customKvps |
Optional closure that returns custom key-value pairs to attach to AdMob requests for targeting. |
enableBannerAdPriority |
Optional flag that changes the fullscreen Ad loading order to banner first, then native fallback. This flag only has an effect when bannerAdUnit is configured. |
For a complete integration example, see our Showcase app code here.
Setup with Banner Priority Enabled#
import StorytellerSDK
import StorytellerGAMIntegration
let configuration = StorytellerAdMobModuleConfiguration(
adUnit: { _ in
"YOUR_NATIVE_AD_UNIT_ID"
},
bannerAdUnit: { _ in
"YOUR_BANNER_AD_UNIT_ID"
},
enableBannerAdPriority: true
)
Storyteller.shared.modules = [StorytellerAdMobModule(configuration: configuration)]
Setup with Dynamic Ad Unit Changes#
import StorytellerSDK
import StorytellerGAMIntegration
let configuration = StorytellerAdMobModuleConfiguration(
adUnit: { requestInfo in
switch requestInfo {
case .stories:
return "YOUR_STORIES_NATIVE_AD_UNIT_ID"
case .clips:
return "YOUR_CLIPS_NATIVE_AD_UNIT_ID"
}
},
bannerAdUnit: { requestInfo in
switch requestInfo {
case .stories:
return "YOUR_STORIES_BANNER_AD_UNIT_ID"
case .clips:
return "YOUR_CLIPS_BANNER_AD_UNIT_ID"
}
},
bottomBannerAdUnit: { requestInfo in
switch requestInfo {
case .stories:
return "YOUR_STORIES_BOTTOM_BANNER_AD_UNIT_ID"
case .clips:
return "YOUR_CLIPS_BOTTOM_BANNER_AD_UNIT_ID"
}
},
customKvps: {
["appmode": "prod"]
},
enableBannerAdPriority: true
)
AdMob Test IDs for Local Validation#
The Showcase app uses the following sample AdMob unit IDs for local validation. Use your own production IDs outside test and debug flows.
| Placement | Sample AdMob unit ID |
|---|---|
| Native | ca-app-pub-3940256099942544/3986624511 |
| Native video | ca-app-pub-3940256099942544/2521693316 |
| Fullscreen banner fallback | ca-app-pub-3940256099942544/2435281174 |
| Clips bottom banner | ca-app-pub-3940256099942544/2934735716 |
AdMob vs GAM at a Glance#
| Concern | GAM | AdMob |
|---|---|---|
| Module entry point | StorytellerGAMModule |
StorytellerAdMobModule |
| Native-specific options | Supports customNativeTemplateIds for custom native Ads |
Uses standard native Ads and does not expose customNativeTemplateIds |
| Fullscreen banner setup | No separate bannerAdUnit parameter |
Uses optional bannerAdUnit; this can be banner fallback or banner-first when enableBannerAdPriority is true |
| Shared options | Supports bottomBannerAdUnit and customKvps |
Supports bottomBannerAdUnit, customKvps, and enableBannerAdPriority |
Mutual Exclusivity#
StorytellerGAMModule and StorytellerAdMobModule are mutually exclusive. Configure only one of them at a time.
Bottom Banner Ads#
The Clips Player supports bottom banner Ads rendered as standard banner views added to the hierarchy below the video view. When using the GAM or AdMob module, supply bottomBannerAdUnit in the corresponding configuration to fetch bottom banner Ads.
Ad Request Information#
Note: This section is only relevant if you're implementing a custom Ads solution. If you're using Storyteller First Party Ads, StorytellerGAMModule, or StorytellerAdMobModule, you don't need to work with this directly.
If your StorytellerModule (or StorytellerDelegate) provides integrating-app ads, set adSource to declare the source used by your implementation.
- For custom ad implementations, use
.custom("myNetwork"). - For Google modules, use
.gamfor GAM and.admobfor AdMob. .storytelleris reserved for Storyteller First Party ads.
Setting adSource to .gam or .admob enables Google paid ad analytics events. See Ad Events.
StorytellerGAMModule and StorytellerAdMobModule set adSource automatically.
When implementing custom Ads, you'll receive context about the Ad request through the StorytellerAdRequestInfo enum. This provides information about what content the Ad will be displayed for.
StorytellerAdRequestInfo#
The StorytellerAdRequestInfo enum has two cases:
-
stories(placement: String, categories: [String], story: ItemInfo)Used when an Ad is requested for display in a Stories Player. The parameters include:
-
placement- The placement identifier of the Story categories- An array of categories associated with the List that the Story is part of-
story- AnItemInfostruct containing more information about the specific Story -
clips(collection: String, clip: ItemInfo, nextClip: ItemInfo?, adIndex: Int)Used when an Ad is requested for display in a Clips Player. The parameters include:
-
collection- The identifier of the Clip collection clip- AnItemInfostruct containing detailed information about the current ClipnextClip- An optionalItemInfostruct containing more information about the Clip that is to appear after the requested AdadIndex- The order of the Ad within the displayed Ads in a Clip collection (1 for the first Ad, 2 for the second, etc.)
ItemInfo#
Each case includes an ItemInfo struct that contains:
categories- An array ofCategoryobjects representing categories that the Story or Clip is part of.
Non Skippable Ads#
Our Player can enforce a period of time during which ads can't be skipped. When enabled, user interactions that would skip a Story or Clip Ad won't be allowed for that duration. This feature can be configured in the CMS.