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 var rewardedZoneManager: EmpowerZoneManager?
override func viewDidLoad() {
super.viewDidLoad()
loadRewardedAd()
}
func loadRewardedAd() {
rewardedZoneManager = EMAManager.shared.loadRewarded(
zoneId: "YOUR_REWARDED_ZONE_ID",
delegate: self
)
}
@IBAction func watchAdButtonTapped(_ sender: UIButton) {
guard let manager = rewardedZoneManager,
manager.isRewardedReady() else {
showAlert("Ad not ready", "Please try again in a moment.")
return
}
manager.showRewarded()
}
// Called when user COMPLETES the ad
func empowerRewardedAdStatusChanged(_ manager: EmpowerRewardedManager) {
if manager.status == .rewarded {
grantReward()
loadRewardedAd() // Preload next
}
}
func rewardedStatusChanged(_ manager: EmpowerRewardedManager) {
switch manager.status {
case .ready:
enableWatchAdButton()
case .failed:
disableWatchAdButton()
default:
break
}
}
private func grantReward() {
// Grant 100 coins to user
UserManager.shared.addCoins(100)
showAlert("Reward Earned!", "You received 100 coins!")
}
}

Important: Reward Callback

There are two delegate methods for rewarded ads:

MethodWhen CalledUse For
rewardedStatusChanged(_:)Status changes (loading, ready, etc.)UI updates
empowerRewardedAdStatusChanged(_:)User completed watchingGranting rewards

Always use empowerRewardedAdStatusChanged for granting rewards. The .rewarded status is only triggered when the user successfully completes watching the entire ad.

// CORRECT - Grant reward here
func empowerRewardedAdStatusChanged(_ manager: EmpowerRewardedManager) {
if manager.status == .rewarded {
grantReward() // User earned the reward
}
}
// WRONG - Don't grant reward here
func rewardedStatusChanged(_ manager: EmpowerRewardedManager) {
if manager.status == .used {
// This is called when ad closes, NOT when reward is earned
// User might have skipped the ad
}
}

Loading Rewarded Ads

Basic Loading

rewardedZoneManager = EMAManager.shared.loadRewarded(
zoneId: "YOUR_ZONE_ID",
delegate: self
)

With Custom Parameters

let customParams: [String: [String]] = [
"user_level": ["25"],
"reward_type": ["coins"]
]
rewardedZoneManager = EMAManager.shared.loadRewarded(
zoneId: "YOUR_ZONE_ID",
delegate: self,
customParameters: customParams
)

Showing Rewarded Ads

Check Readiness

func showRewardedAd() {
guard let manager = rewardedZoneManager else {
print("Rewarded manager not initialized")
return
}
if manager.isRewardedReady() {
manager.showRewarded()
} else {
showAlert("Not Ready", "Ad is still loading. Please try again.")
}
}

With Confirmation Dialog

@IBAction func watchAdButtonTapped(_ sender: UIButton) {
let alert = UIAlertController(
title: "Watch Ad for Coins?",
message: "Watch a short video to earn 100 coins!",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "Watch", style: .default) { [weak self] _ in
self?.showRewardedAd()
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(alert, animated: true)
}

Status Handling

Complete Implementation

extension StoreViewController: AdStatusDelegate {
// Status updates (loading, ready, failed, etc.)
func rewardedStatusChanged(_ manager: EmpowerRewardedManager) {
switch manager.status {
case .initializing:
// Ad is loading
watchAdButton.isEnabled = false
watchAdButton.setTitle("Loading...", for: .normal)
case .ready:
// Ad is ready to show
watchAdButton.isEnabled = true
watchAdButton.setTitle("Watch Ad for 100 Coins", for: .normal)
case .failed:
// Ad failed to load
watchAdButton.isEnabled = false
watchAdButton.setTitle("Ad Unavailable", for: .normal)
// Retry after delay
retryLoadAfterDelay()
case .present:
// Ad is showing
pauseBackgroundMusic()
case .used:
// Ad was closed (reward may or may not have been earned)
resumeBackgroundMusic()
loadRewardedAd() // Preload next
default:
break
}
}
// REWARD CALLBACK - User completed watching
func empowerRewardedAdStatusChanged(_ manager: EmpowerRewardedManager) {
if manager.status == .rewarded {
// USER EARNED THE REWARD
grantReward()
}
}
private func grantReward() {
// Add coins
let coinsEarned = 100
UserManager.shared.addCoins(coinsEarned)
// Show success message
showRewardAlert(coins: coinsEarned)
// Analytics
Analytics.track("reward_earned", ["coins": coinsEarned])
}
private func retryLoadAfterDelay() {
DispatchQueue.main.asyncAfter(deadline: .now() + 30) { [weak self] in
self?.loadRewardedAd()
}
}
}

Status Flow

loadRewarded() → .initializing → .ready → showRewarded() → .present → User watches → .rewarded → .used
↓ ↓
.failed User skips → .used (no reward)

Status Values

StatusDescription
.initializingAd is loading
.readyAd is ready to show
.presentAd is currently showing
.rewardedUser completed ad and earned reward
.failedAd failed to load
.usedAd was closed

SwiftUI Integration

import SwiftUI
import EmpowerMobileAds
// MARK: - Rewarded Ad Coordinator
class RewardedAdCoordinator: NSObject, ObservableObject, AdStatusDelegate {
@Published var isReady = false
@Published var isLoading = true
@Published var rewardEarned = false
private var zoneManager: EmpowerZoneManager?
private let zoneId: String
var onRewardEarned: ((Int) -> Void)?
init(zoneId: String) {
self.zoneId = zoneId
super.init()
load()
}
func load() {
isLoading = true
zoneManager = EMAManager.shared.loadRewarded(
zoneId: zoneId,
delegate: self
)
}
func show() {
guard isReady, let manager = zoneManager else { return }
manager.showRewarded()
}
// Status updates
func rewardedStatusChanged(_ manager: EmpowerRewardedManager) {
DispatchQueue.main.async {
switch manager.status {
case .ready:
self.isReady = true
self.isLoading = false
case .failed:
self.isReady = false
self.isLoading = false
case .used:
self.isReady = false
self.load()
default:
break
}
}
}
// Reward callback
func empowerRewardedAdStatusChanged(_ manager: EmpowerRewardedManager) {
if manager.status == .rewarded {
DispatchQueue.main.async {
self.rewardEarned = true
self.onRewardEarned?(100)
}
}
}
}
// 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()
}) {
HStack {
Image(systemName: "play.circle.fill")
Text(buttonTitle)
}
.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 100 coins!")
}
}
private var buttonTitle: String {
if rewardedAd.isLoading {
return "Loading..."
} else if rewardedAd.isReady {
return "Watch Ad for 100 Coins"
} else {
return "Ad Unavailable"
}
}
}

Troubleshooting

Reward Not Granted

  1. Ensure you're using the correct callback:
// Use THIS for rewards
func empowerRewardedAdStatusChanged(_ manager: EmpowerRewardedManager) {
if manager.status == .rewarded {
grantReward()
}
}
  1. Check delegate is set correctly

  2. Verify ad completed (not skipped)

Ad Not Loading

// Debug state
print("Manager exists: \(rewardedZoneManager != nil)")
print("Is ready: \(rewardedZoneManager?.isRewardedReady() ?? false)")
// Enable logging
EMASettings.shared.logLevel = .all