Skip to main content

Banner Ads

Banner ads are rectangular ads that occupy a portion of your app's layout. They can refresh automatically and stay on screen while users interact with your app.

Each ad format uses a Zone ID to identify the ad placement. Zone IDs are configured in the Empower dashboard.

Note: All ad status listeners are optional. The SDK handles ad loading and display automatically. Use listeners only if you need to track ad states for analytics, UI updates, or custom logic.


Quick Start

import EmpowerMobileAds

class ViewController: UIViewController, AdStatusDelegate {

@IBOutlet weak var bannerContainer: UIView!

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadBanner()
}

func loadBanner() {
EMAManager.shared.loadBannerAd(
viewController: self,
zoneId: "YOUR_BANNER_ZONE_ID",
bannerContainer: bannerContainer,
delegate: self
)
}

func bannerStatusChanged(status: AdStatus, zoneId: String, adUnitId: String) {
switch status {
case .ready:
print("Banner is ready and displayed for zone: \(zoneId)")
case .failed(let error):
print("Banner failed to load: \(error?.localizedDescription ?? "unknown")")
case .initializing:
print("Banner is loading...")
default:
break
}
}
}

Supported Sizes

SizeDescription
320x50Standard Banner
320x100Large Banner
300x250Medium Rectangle

Adaptive & Custom Sizes

By default a banner renders at the backend-configured size (.fixed). You can override the size for a single load with the sizeMode parameter — the ad unit IDs still come from the backend, so this changes only how the ad is sized and rendered, not which units are requested. Anchored adaptive sizes generally earn more than a fixed 320×50.

EMABannerSizeModeBehavior
.fixed (default)Backend-provided adSize — unchanged behavior.
.adaptiveAnchoredAnchored adaptive: Google standard anchored + AppLovin adaptive, both resolved to the same height for the container width (tight fit).
.adaptiveAnchoredLargeGoogle's large anchored variant (taller → higher revenue); AppLovin serves its standard adaptive height, centered in the taller container.
.custom(width:height:)Caller-provided size. width == 0 means "use the container's width". Google / own-view honor the exact size; AppLovin renders its nearest fixed format centered.
EMAManager.shared.loadBannerAd(
viewController: self,
zoneId: "YOUR_BANNER_ZONE_ID",
bannerContainer: bannerContainer,
delegate: self,
sizeMode: .adaptiveAnchored
)

Notes: size the container for the adaptive height (anchored adaptive is often taller than 50pt), and for anchored modes pin the container's width to the screen. MREC (300×250) zones ignore anchored modes (they stay 300×250); .custom is always honored. Adaptive/custom controls the request size — fill still needs matching creatives trafficked on the ad unit, so an unusual custom size may simply no-fill.


Container Setup

Interface Builder (Storyboard)

  1. Add a UIView to your view controller
  2. Set constraints for position and height
  3. Connect the outlet to your view controller

Programmatic Setup

// Standard Banner (320x50)
let bannerContainer = UIView()
bannerContainer.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(bannerContainer)

NSLayoutConstraint.activate([
bannerContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
bannerContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor),
bannerContainer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
bannerContainer.heightAnchor.constraint(equalToConstant: 50)
])
// MREC (300x250)
let mrecContainer = UIView()
mrecContainer.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mrecContainer)

NSLayoutConstraint.activate([
mrecContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor),
mrecContainer.topAnchor.constraint(equalTo: someView.bottomAnchor, constant: 16),
mrecContainer.widthAnchor.constraint(equalToConstant: 300),
mrecContainer.heightAnchor.constraint(equalToConstant: 250)
])

Loading Options

Basic Loading

EMAManager.shared.loadBannerAd(
viewController: self,
zoneId: "YOUR_ZONE_ID",
bannerContainer: bannerContainer,
delegate: self
)

With Keywords

EMAManager.shared.loadBannerAd(
viewController: self,
zoneId: "YOUR_ZONE_ID",
bannerContainer: bannerContainer,
delegate: self,
keywords: "sports,football,news"
)

Legacy Loading (without ViewController)

Note: It's recommended to use the method that accepts a viewController parameter for proper lifecycle management. In Objective-C, use loadBannerAdWithViewController:zoneId:container:observer:keywords:.

EMAManager.shared.loadBannerAd(
zoneId: "YOUR_ZONE_ID",
container: bannerContainer,
delegate: self,
customParameters: "sports,football"
)

In a List (UITableView / UICollectionView)

For banners inside recycling cells, use the cell-aware load method — pass the cell so the SDK ties the ad to the cell's reuse. A recycled cell then never shows a stale ad from another row, and the zone is paused/resumed as the row scrolls in and out, instead of being torn down and reloaded on every recycle. Don't use the plain loadBannerAd for cells.

Add the container to cell.contentView, then load with the cell (from cellForRowAt):

EMAManager.shared.loadBannerAd(
cell: cell,
zoneId: "YOUR_ZONE_ID",
bannerContainer: container, // already added to cell.contentView
delegate: self
)

That's all. When you load with the cell: API the SDK observes the cell directly and tracks its on-screen visibility and recycling automatically — it pauses the ad when the row scrolls off-screen, resumes it when it returns, and clears it on cell reuse. You do not need to hook willDisplay / didEndDisplaying or call notifyBannerVisible / notifyBannerInvisible.

In a Scroll View (or any other scrolling container)

For a banner inside a plain UIScrollView, a SwiftUI ScrollView, a page / carousel view, or any container that is not a recycling UITableView / UICollectionView cell, just place the banner container in your layout and load it the normal way with loadBannerAd(viewController:zoneId:bannerContainer:delegate:). The SDK tracks the container's actual on-screen position and automatically pauses the ad when it scrolls off-screen and resumes it when it comes back — there is nothing extra to call. Visibility is geometry-based: the ad counts as visible while roughly ≥10% of the container is on screen.

Only recycling cells need the cell: API above (for reuse correctness); every other scrolling layout is handled by this automatic tracking.


Status Handling

Status Callback

Adopt AdStatusDelegate and implement bannerStatusChanged(status:zoneId:adUnitId:):

extension ViewController: AdStatusDelegate {

func bannerStatusChanged(status: AdStatus, zoneId: String, adUnitId: String) {
switch status {
case .initializing:
// Banner is loading
showLoadingIndicator()

case .ready:
// Banner is displayed
hideLoadingIndicator()
print("Banner loaded for zone: \(zoneId), adUnit: \(adUnitId)")

case .failed(let error):
// Banner failed to load
hideLoadingIndicator()
hideBannerContainer()
print("Banner failed for zone: \(zoneId), error: \(error?.localizedDescription ?? "unknown")")

case .clicked:
print("Banner clicked for zone: \(zoneId)")

case .impression:
print("Banner impression recorded for zone: \(zoneId)")

default:
break
}
}
}

Status Values

Swift (AdStatus)Objective-C (EMAAdStatusType)Description
.initializingEMAAdStatusTypeInitializingBanner is loading
.readyEMAAdStatusTypeReadyBanner is displayed
.failed(Error?)EMAAdStatusTypeFailedBanner failed to load
.clickedEMAAdStatusTypeClickedBanner was clicked
.impressionEMAAdStatusTypeImpressionImpression was recorded
.undefinedEMAAdStatusTypeUndefinedInitial/destroyed state

SwiftUI Integration

import SwiftUI
import EmpowerMobileAds

// MARK: - Banner View

struct BannerAdView: UIViewControllerRepresentable {
let zoneId: String
let height: CGFloat

func makeUIViewController(context: Context) -> BannerAdViewController {
return BannerAdViewController(zoneId: zoneId)
}

func updateUIViewController(_ uiViewController: BannerAdViewController, context: Context) {}
}

// MARK: - Banner View Controller

class BannerAdViewController: UIViewController, AdStatusDelegate {
private let zoneId: String
private var bannerContainer: UIView!

init(zoneId: String) {
self.zoneId = zoneId
super.init(nibName: nil, bundle: nil)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()
setupContainer()
loadBanner()
}

private func setupContainer() {
bannerContainer = UIView()
bannerContainer.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(bannerContainer)

NSLayoutConstraint.activate([
bannerContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
bannerContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor),
bannerContainer.topAnchor.constraint(equalTo: view.topAnchor),
bannerContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}

private func loadBanner() {
EMAManager.shared.loadBannerAd(
viewController: self,
zoneId: zoneId,
bannerContainer: bannerContainer,
delegate: self
)
}

func bannerStatusChanged(status: AdStatus, zoneId: String, adUnitId: String) {
// Handle status changes
}
}

// MARK: - Usage

struct ContentView: View {
var body: some View {
VStack {
Text("Your Content")
Spacer()

// Standard Banner
BannerAdView(zoneId: "YOUR_BANNER_ZONE_ID", height: 50)
.frame(height: 50)
}
}
}

struct ArticleView: View {
var body: some View {
ScrollView {
VStack {
Text("Article Title")
.font(.title)

// MREC in content
BannerAdView(zoneId: "YOUR_MREC_ZONE_ID", height: 250)
.frame(height: 250)

Text("Article content...")
}
}
}
}

Lifecycle Management

Zone Control

// Pause banner refresh timer
EMAManager.shared.pauseBannerZone(zoneId: "YOUR_ZONE_ID")

// Resume banner refresh timer
EMAManager.shared.resumeBannerZone(zoneId: "YOUR_ZONE_ID")

// Destroy banner zone completely
EMAManager.shared.destroyBannerZone(zoneId: "YOUR_ZONE_ID")

Banners already pause and resume automatically based on their on-screen position (see In a Scroll View), so you rarely need these. For edge cases where you want to drive visibility explicitly — e.g. a custom container whose geometry the SDK can't read reliably — signal it yourself:

EMAManager.shared.notifyBannerVisible(zoneId: "YOUR_ZONE_ID")
EMAManager.shared.notifyBannerInvisible(zoneId: "YOUR_ZONE_ID")

Tab Bar Navigation

When using tab bar controllers, pause and resume banners on inactive tabs:

class TabBarController: UITabBarController, UITabBarControllerDelegate {

override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}

func tabBarController(_ tabBarController: UITabBarController,
didSelect viewController: UIViewController) {
// Pause all banners on previously visible tabs
// Resume banners on the newly selected tab
}
}

Auto-Refresh

Banner ads automatically refresh based on server configuration. The SDK handles:

  • Pausing refresh when view is not visible
  • Pausing refresh when app is in background
  • Resuming refresh when view becomes visible again

You don't need to manually reload banners for refresh - it's automatic.


Best Practices

1. Container Size

For fixed-size banners, set an explicit height constraint (adaptive banners size themselves — see best practice 7):

// Good
bannerContainer.heightAnchor.constraint(equalToConstant: 50).isActive = true

// Avoid - may cause layout issues
bannerContainer.heightAnchor.constraint(greaterThanOrEqualToConstant: 50).isActive = true

2. Safe Area

Position banners within safe area:

// Good - respects safe area
bannerContainer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)

// Avoid - may overlap with home indicator
bannerContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor)

3. Load in viewWillAppear

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadBanner() // Load when view is about to appear
}

4. Handle Failures Gracefully

func bannerStatusChanged(status: AdStatus, zoneId: String, adUnitId: String) {
if case .failed = status {
// Option 1: Hide container
bannerContainer.isHidden = true

// Option 2: Show fallback content
showFallbackContent()

// Option 3: Collapse container height
bannerHeightConstraint.constant = 0
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
}

5. Use the Cell API in Recycling Lists

For banners in UITableViewCell / UICollectionViewCell, load with the cell-aware loadBannerAd(cell:) (see In a List) so a recycled cell never shows a stale ad from another row. Don't use the plain loadBannerAd for recycling cells.

6. Let the SDK Manage Visibility

Banners pause and resume themselves based on their on-screen position in any scroll container (see In a Scroll View). Don't destroy and reload a banner as it scrolls off and back on screen, and don't manually pause/resume it — the SDK already does this, and reloading on every scroll wastes requests and hurts viewability.

7. Prefer Adaptive Sizes

Anchored adaptive (.adaptiveAnchored / .adaptiveAnchoredLarge) generally earns more than a fixed 320×50 — see Adaptive & Custom Sizes. Give adaptive banners a flexible height rather than a hard 50pt constraint.


Troubleshooting

  1. Check container visibility:
print("Hidden: \(bannerContainer.isHidden)")
print("Alpha: \(bannerContainer.alpha)")
print("Frame: \(bannerContainer.frame)")
  1. Check container is in view hierarchy:
print("Superview: \(bannerContainer.superview)")
  1. Check zone ID is correct

  2. Enable debug logging:

EMASettings.shared.logLevel = .all

The SDK loads ads from multiple sources in parallel and selects the best one. This optimization may take a moment. You can show a placeholder:

func bannerStatusChanged(status: AdStatus, zoneId: String, adUnitId: String) {
switch status {
case .initializing:
showPlaceholder()
case .ready:
hidePlaceholder()
default:
break
}
}

API Reference

func loadBannerAd(
viewController: UIViewController,
zoneId: String,
bannerContainer: UIView,
delegate: AdStatusDelegate? = nil,
keywords: String? = nil,
sizeMode: EMABannerSizeMode = .fixed
)
ParameterTypeDescription
viewControllerUIViewControllerThe hosting view controller (for lifecycle management)
zoneIdStringYour banner zone ID
bannerContainerUIViewContainer view for the banner
delegateAdStatusDelegate?Callback delegate
keywordsString?Targeting keywords
sizeModeEMABannerSizeModePer-load sizing override (default .fixed). See Adaptive & Custom Sizes.

EMAManager.loadBannerAd (Legacy)

func loadBannerAd(
zoneId: String,
container: UIView,
delegate: AdStatusDelegate? = nil,
customParameters: String? = nil
)
ParameterTypeDescription
zoneIdStringYour banner zone ID
containerUIViewContainer view for the banner
delegateAdStatusDelegate?Callback delegate
customParametersString?Custom targeting parameters

EMAManager.loadBannerAd (In a Cell)

func loadBannerAd(
cell: UIView,
zoneId: String,
bannerContainer: UIView,
delegate: AdStatusDelegate? = nil,
keywords: String? = nil,
sizeMode: EMABannerSizeMode = .fixed
)

Use for banners inside a UITableViewCell / UICollectionViewCell — pass the cell so the SDK handles reuse. ObjC: loadBannerAdInCell:zoneId:container:observer:keywords:.

Zone Management

func pauseBannerZone(zoneId: String)
func resumeBannerZone(zoneId: String)
func destroyBannerZone(zoneId: String)
func notifyBannerVisible(zoneId: String) // optional explicit signal (banners auto-track by geometry)
func notifyBannerInvisible(zoneId: String) // optional explicit signal (banners auto-track by geometry)

Objective-C selectors

SwiftObjective-C selector
loadBannerAd(viewController:zoneId:bannerContainer:delegate:keywords:)loadBannerAdWithViewController:zoneId:container:observer:keywords:
loadBannerAd(…sizeMode:) (adaptive/custom)loadBannerAdWithViewController:zoneId:container:observer:keywords:adaptiveMode:customWidth:customHeight:
pauseBannerZone(zoneId:)pauseBannerZone:
resumeBannerZone(zoneId:)resumeBannerZone:
destroyBannerZone(zoneId:)destroyBannerZone:
notifyBannerVisible(zoneId:)notifyBannerVisible:
notifyBannerInvisible(zoneId:)notifyBannerInvisible:

In Objective-C, adopt EMAAdObserver and pass your object as the observer: argument. Status is delivered as EMAAdStatusType via bannerStatusChanged:zoneId:adUnitId:.