Rewarded Ads

Rewarded ads let users opt-in to watch a video ad in exchange for an in-app reward (extra lives, premium content, virtual currency, etc.).

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 StoreViewController: UIViewController, AdStatusDelegate {
private let rewardedZoneId = "YOUR_REWARDED_ZONE_ID"
override func viewDidLoad() {
super.viewDidLoad()
loadRewardedAd()
}
func loadRewardedAd() {
EMAManager.shared.loadRewardedAd(zoneId: rewardedZoneId, delegate: self)
}
@IBAction func watchAdButtonTapped(_ sender: UIButton) {
if EMAManager.shared.isRewardedReady(zoneId: rewardedZoneId) {
EMAManager.shared.showRewarded(zoneId: rewardedZoneId, from: self)
} else {
showAlert("Ad not ready", "Please try again in a moment.")
}
}
func rewardedStatusChanged(adStatus: AdStatus) {
switch adStatus {
case .ready:
enableWatchAdButton()
case .rewarded(let type, let amount):
// USER COMPLETED THE AD — grant the reward
grantReward(amount)
case .closed:
loadRewardedAd() // Preload next
case .failed:
disableWatchAdButton()
default:
break
}
}
private func grantReward(_ amount: Int) {
UserManager.shared.addCoins(amount)
showAlert("Reward Earned!", "You received \(amount) coins!")
}
}

Important: Reward Callback

Rewarded status updates are delivered through the single status callback (rewardedStatusChanged in Swift, rewardedStatusChanged:rewardType:rewardAmount: in Objective-C).

Grant the reward only when the status is rewarded. This status fires exactly once, when the user successfully finishes watching the ad. If the user closes or skips the ad early, you receive closed without a preceding rewarded, so no reward is granted.

func rewardedStatusChanged(adStatus: AdStatus) {
switch adStatus {
case .rewarded(let type, let amount):
// CORRECT — user earned the reward
grantReward(amount)
case .closed:
// Ad dismissed (reward may or may not have been earned). Do NOT grant here.
loadRewardedAd()
default:
break
}
}

The rewardType / rewardAmount parameters (Objective-C) and the .rewarded(type:amount:) associated values (Swift) carry the reward payload configured for the zone.


Loading Rewarded Ads

EMAManager.shared.loadRewardedAd(zoneId: "YOUR_ZONE_ID", delegate: self)

Auto-Preload

Enable auto-preload so the next ad is loaded automatically when the current one is shown, keeping an ad ready at all times:

EMAManager.shared.setRewardedAutoPreload(enabled: true, zoneId: "YOUR_ZONE_ID")

Showing Rewarded Ads

Always verify the ad is ready before showing:

func showRewardedAd() {
let zoneId = "YOUR_ZONE_ID"
if EMAManager.shared.isRewardedReady(zoneId: zoneId) {
EMAManager.shared.showRewarded(zoneId: zoneId, from: self)
} else {
showAlert("Not Ready", "Ad is still loading. Please try again.")
}
}

Status Handling

extension StoreViewController: AdStatusDelegate {
func rewardedStatusChanged(adStatus: AdStatus) {
switch adStatus {
case .initializing:
watchAdButton.isEnabled = false
watchAdButton.setTitle("Loading...", for: .normal)
case .ready:
watchAdButton.isEnabled = true
watchAdButton.setTitle("Watch Ad for 100 Coins", for: .normal)
case .shown:
pauseBackgroundMusic()
case .rewarded(_, let amount):
// User completed the ad — grant the reward
grantReward(amount)
case .closed:
resumeBackgroundMusic()
loadRewardedAd() // Preload next
case .failed:
watchAdButton.isEnabled = false
watchAdButton.setTitle("Ad Unavailable", for: .normal)
retryLoadAfterDelay()
default:
break
}
}
private func retryLoadAfterDelay() {
DispatchQueue.main.asyncAfter(deadline: .now() + 30) { [weak self] in
self?.loadRewardedAd()
}
}
}

Status Flow

loadRewardedAd() → .initializing → .ready → showRewarded() → .shown → user completes → .rewarded → .closed
↓ ↓
.failed user skips → .closed (no reward)

Status Values

Swift (AdStatus)Objective-C (EMAAdStatusType)Description
.initializingEMAAdStatusTypeInitializingAd is loading
.readyEMAAdStatusTypeReadyAd is ready to show
.shownEMAAdStatusTypeShownAd is currently showing
.rewarded(type:amount:)EMAAdStatusTypeRewardedUser completed the ad and earned the reward
.failed(Error?)EMAAdStatusTypeFailedAd failed to load
.closedEMAAdStatusTypeClosedAd was closed/dismissed

Releasing a Zone

EMAManager.shared.destroyRewardedZone(zoneId: "YOUR_ZONE_ID")

SwiftUI Integration

import SwiftUI
import EmpowerMobileAds
// MARK: - Rewarded Ad Coordinator
class RewardedAdCoordinator: NSObject, ObservableObject, AdStatusDelegate {
@Published var isReady = false
@Published var isLoading = true
private let zoneId: String
var onRewardEarned: ((Int) -> Void)?
init(zoneId: String) {
self.zoneId = zoneId
super.init()
load()
}
func load() {
isLoading = true
EMAManager.shared.loadRewardedAd(zoneId: zoneId, delegate: self)
}
func show() {
guard isReady,
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootVC = windowScene.windows.first?.rootViewController else { return }
EMAManager.shared.showRewarded(zoneId: zoneId, from: rootVC)
}
func rewardedStatusChanged(adStatus: AdStatus) {
DispatchQueue.main.async {
switch adStatus {
case .ready:
self.isReady = true
self.isLoading = false
case .failed:
self.isReady = false
self.isLoading = false
case .rewarded(_, let amount):
self.onRewardEarned?(amount)
case .closed:
self.isReady = false
self.load() // Reload
default:
break
}
}
}
}
// MARK: - Usage in SwiftUI View
struct StoreView: View {
@StateObject private var rewardedAd = RewardedAdCoordinator(zoneId: "YOUR_ZONE_ID")
@State private var coins = 0
@State private var showRewardAlert = false
var body: some View {
VStack(spacing: 20) {
Text("Coins: \(coins)")
.font(.largeTitle)
Button(action: { rewardedAd.show() }) {
Text(rewardedAd.isReady ? "Watch Ad for 100 Coins" : "Ad Unavailable")
.frame(maxWidth: .infinity)
.padding()
.background(rewardedAd.isReady ? Color.blue : Color.gray)
.foregroundColor(.white)
.cornerRadius(10)
}
.disabled(!rewardedAd.isReady)
}
.padding()
.onAppear {
rewardedAd.onRewardEarned = { amount in
coins += amount
showRewardAlert = true
}
}
.alert("Reward Earned!", isPresented: $showRewardAlert) {
Button("OK", role: .cancel) {}
} message: {
Text("You received your reward!")
}
}
}

Troubleshooting

Reward Not Granted

  1. Grant the reward in the rewarded status only (not on closed).
  2. Check the delegate/observer is set when loading.
  3. Verify the ad completed (a skipped ad never reports rewarded).

Ad Not Loading

print("Is ready: \(EMAManager.shared.isRewardedReady(zoneId: "YOUR_ZONE_ID"))")
EMASettings.shared.logLevel = .all

API Reference

func loadRewardedAd(zoneId: String, delegate: AdStatusDelegate? = nil)
func showRewarded(zoneId: String, from viewController: UIViewController)
func isRewardedReady(zoneId: String) -> Bool
func setRewardedAutoPreload(enabled: Bool, zoneId: String)
func destroyRewardedZone(zoneId: String)

Objective-C selectors

SwiftObjective-C selector
loadRewardedAd(zoneId:delegate:)loadRewardedAdWithZoneId:observer:
showRewarded(zoneId:from:)showRewardedWithZoneId:fromViewController:
isRewardedReady(zoneId:)isRewardedReadyForZoneId:
setRewardedAutoPreload(enabled:zoneId:)setRewardedAutoPreloadEnabled:forZoneId:
destroyRewardedZone(zoneId:)destroyRewardedZone:

In Objective-C, adopt EMAAdObserver and implement rewardedStatusChanged:rewardType:rewardAmount:. Grant the reward when status is EMAAdStatusTypeRewarded.