> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adrop.io/llms.txt
> Use this file to discover all available pages before exploring further.

# リワード広告

> iOSアプリでリワード動画広告を実装する方法を案内します。

## 概要

リワード広告は、ユーザーが動画広告を最後まで視聴すると、ゲーム内通貨、ライフ、ヒントなどの報酬を提供するフルスクリーン動画広告です。

### 主な特徴

* フルスクリーンで表示される動画広告
* ユーザーが広告を完全に視聴した場合にのみ報酬を提供
* ユーザーが自ら広告視聴を選択 (例: 「動画を見てライフを獲得」ボタン)
* 報酬タイプと数量を設定可能

<Note>
  開発環境ではテストユニットIDを使用してください: `PUBLIC_TEST_UNIT_ID_REWARDED`
</Note>

***

## 実装方法

リワード広告は次の手順で実装します:

1. **AdropRewardedAdインスタンスの生成** - ユニットIDで広告オブジェクトを生成
2. **デリゲートの設定** - 広告イベント処理のためのdelegateを指定
3. **広告の読み込み** - `load()`メソッドで広告をリクエスト
4. **広告の表示** - `show()`メソッドで広告を表示し、報酬を処理

***

## Swift例

### 基本実装

```swift theme={null}
import UIKit
import AdropAds

class RewardedViewController: UIViewController {
    private var rewardedAd: AdropRewardedAd?

    override func viewDidLoad() {
        super.viewDidLoad()

        // 1. AdropRewardedAdインスタンスの生成
        rewardedAd = AdropRewardedAd(unitId: "YOUR_UNIT_ID")

        // 2. デリゲートの設定
        rewardedAd?.delegate = self

        // 3. 広告の読み込み
        rewardedAd?.load()
    }

    @IBAction func showRewardedAdButtonTapped(_ sender: UIButton) {
        // 広告が読み込まれたか確認
        guard rewardedAd?.isLoaded == true else {
            print("広告がまだ読み込まれていません")
            return
        }

        // 4. 広告の表示と報酬処理
        rewardedAd?.show(fromRootViewController: self) { [weak self] type, amount in
            // 報酬付与処理
            print("報酬獲得 - タイプ: \(type), 数量: \(amount)")
            self?.grantReward(type: type, amount: amount)
        }
    }

    private func grantReward(type: Int, amount: Int) {
        // ユーザーに報酬を付与
        // 例: ゲーム通貨の追加、ライフの回復など

        // ⚠️ 重要: サーバーで報酬を検証することを推奨
        // クライアントのみで報酬を処理すると不正行為が可能
    }
}

// MARK: - AdropRewardedAdDelegate
extension RewardedViewController: AdropRewardedAdDelegate {
    // 必須: 広告受信成功
    func onAdReceived(_ ad: AdropRewardedAd) {
        print("広告受信完了")
        // 広告視聴ボタンを有効化するなど
    }

    // 必須: 広告受信失敗
    func onAdFailedToReceive(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode) {
        print("広告受信失敗: \(errorCode)")
        // 広告視聴ボタンを無効状態に維持するなど
    }

    // オプション: 広告表示
    func onAdImpression(_ ad: AdropRewardedAd) {
        print("広告表示")
    }

    // オプション: 広告クリック
    func onAdClicked(_ ad: AdropRewardedAd) {
        print("広告クリック")
    }

    // オプション: フルスクリーン広告表示直前
    func onAdWillPresentFullScreen(_ ad: AdropRewardedAd) {
        print("広告表示予定")
        // BGMの一時停止など
    }

    // オプション: フルスクリーン広告表示完了
    func onAdDidPresentFullScreen(_ ad: AdropRewardedAd) {
        print("広告表示完了")
    }

    // オプション: フルスクリーン広告閉じる直前
    func onAdWillDismissFullScreen(_ ad: AdropRewardedAd) {
        print("広告閉じる予定")
    }

    // オプション: フルスクリーン広告閉じた後
    func onAdDidDismissFullScreen(_ ad: AdropRewardedAd) {
        print("広告画面を閉じた")
        // BGMの再開など

        // 次の広告を事前読み込み
        rewardedAd?.load()
    }

    // オプション: 広告表示失敗
    func onAdFailedToShowFullScreen(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode) {
        print("広告表示失敗: \(errorCode)")
    }
}
```

### SwiftUI例

```swift theme={null}
import SwiftUI
import AdropAds

class RewardedAdViewModel: NSObject, ObservableObject, AdropRewardedAdDelegate {
    @Published var isAdReady = false
    @Published var showingAlert = false
    @Published var alertMessage = ""

    private var rewardedAd: AdropRewardedAd?

    override init() {
        super.init()
        loadAd()
    }

    func loadAd() {
        rewardedAd = AdropRewardedAd(unitId: "YOUR_UNIT_ID")
        rewardedAd?.delegate = self
        rewardedAd?.load()
    }

    func showAd() {
        guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
              let rootViewController = windowScene.windows.first?.rootViewController else {
            return
        }

        rewardedAd?.show(fromRootViewController: rootViewController) { [weak self] type, amount in
            self?.alertMessage = "報酬獲得! タイプ: \(type), 数量: \(amount)"
            self?.showingAlert = true
            self?.grantReward(type: type, amount: amount)
        }
    }

    private func grantReward(type: Int, amount: Int) {
        // 報酬付与処理
        print("報酬付与 - タイプ: \(type), 数量: \(amount)")
    }

    // MARK: - AdropRewardedAdDelegate

    func onAdReceived(_ ad: AdropRewardedAd) {
        isAdReady = true
    }

    func onAdFailedToReceive(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode) {
        isAdReady = false
        alertMessage = "広告読み込み失敗: \(errorCode)"
        showingAlert = true
    }

    func onAdDidDismissFullScreen(_ ad: AdropRewardedAd) {
        isAdReady = false
        // 次の広告を事前読み込み
        loadAd()
    }

    func onAdFailedToShowFullScreen(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode) {
        alertMessage = "広告表示失敗: \(errorCode)"
        showingAlert = true
    }
}

struct RewardedAdView: View {
    @StateObject private var viewModel = RewardedAdViewModel()

    var body: some View {
        VStack(spacing: 20) {
            Text("リワード広告サンプル")
                .font(.title)

            Button(action: {
                viewModel.showAd()
            }) {
                Text("動画を見て報酬を獲得")
                    .padding()
                    .background(viewModel.isAdReady ? Color.blue : Color.gray)
                    .foregroundColor(.white)
                    .cornerRadius(8)
            }
            .disabled(!viewModel.isAdReady)
        }
        .alert(isPresented: $viewModel.showingAlert) {
            Alert(
                title: Text("通知"),
                message: Text(viewModel.alertMessage),
                dismissButton: .default(Text("確認"))
            )
        }
    }
}
```

***

## Objective-C例

```objc theme={null}
@import AdropAds;

@interface RewardedViewController () <AdropRewardedAdDelegate>
@property (nonatomic, strong) AdropRewardedAd *rewardedAd;
@end

@implementation RewardedViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // 1. AdropRewardedAdインスタンスの生成
    self.rewardedAd = [[AdropRewardedAd alloc] initWithUnitId:@"YOUR_UNIT_ID"];

    // 2. デリゲートの設定
    self.rewardedAd.delegate = self;

    // 3. 広告の読み込み
    [self.rewardedAd load];
}

- (IBAction)showRewardedAdButtonTapped:(UIButton *)sender {
    // 広告が読み込まれたか確認
    if (!self.rewardedAd.isLoaded) {
        NSLog(@"広告がまだ読み込まれていません");
        return;
    }

    // 4. 広告の表示と報酬処理
    __weak typeof(self) weakSelf = self;
    [self.rewardedAd showFromRootViewController:self
                        userDidEarnRewardHandler:^(NSInteger type, NSInteger amount) {
        NSLog(@"報酬獲得 - タイプ: %ld, 数量: %ld", (long)type, (long)amount);
        [weakSelf grantRewardWithType:type amount:amount];
    }];
}

- (void)grantRewardWithType:(NSInteger)type amount:(NSInteger)amount {
    // ユーザーに報酬を付与
}

#pragma mark - AdropRewardedAdDelegate

// 必須: 広告受信成功
- (void)onAdReceived:(AdropRewardedAd *)ad {
    NSLog(@"広告受信完了");
}

// 必須: 広告受信失敗
- (void)onAdFailedToReceive:(AdropRewardedAd *)ad :(AdropErrorCode)errorCode {
    NSLog(@"広告受信失敗: %ld", (long)errorCode);
}

// オプション: 広告表示
- (void)onAdImpression:(AdropRewardedAd *)ad {
    NSLog(@"広告表示");
}

// オプション: 広告クリック
- (void)onAdClicked:(AdropRewardedAd *)ad {
    NSLog(@"広告クリック");
}

// オプション: フルスクリーン広告表示直前
- (void)onAdWillPresentFullScreen:(AdropRewardedAd *)ad {
    NSLog(@"広告表示予定");
}

// オプション: フルスクリーン広告表示完了
- (void)onAdDidPresentFullScreen:(AdropRewardedAd *)ad {
    NSLog(@"広告表示完了");
}

// オプション: フルスクリーン広告閉じる直前
- (void)onAdWillDismissFullScreen:(AdropRewardedAd *)ad {
    NSLog(@"広告閉じる予定");
}

// オプション: フルスクリーン広告閉じた後
- (void)onAdDidDismissFullScreen:(AdropRewardedAd *)ad {
    NSLog(@"広告画面を閉じた");

    // 次の広告を事前読み込み
    [self.rewardedAd load];
}

// オプション: 広告表示失敗
- (void)onAdFailedToShowFullScreen:(AdropRewardedAd *)ad :(AdropErrorCode)errorCode {
    NSLog(@"広告表示失敗: %ld", (long)errorCode);
}

@end
```

***

## デリゲートメソッド

### 必須メソッド

<ParamField path="onAdReceived(_:)" type="void">
  広告の受信に成功したときに呼び出されます。この時点から`show()`メソッドで広告を表示できます。
</ParamField>

<ParamField path="onAdFailedToReceive(_:_:)" type="void">
  広告の受信に失敗したときに呼び出されます。エラーコードで失敗原因を確認できます。
</ParamField>

### オプションメソッド

<ParamField path="onAdImpression(_:)" type="void">
  広告が表示され、表示記録が送信されたときに呼び出されます。
</ParamField>

<ParamField path="onAdClicked(_:)" type="void">
  ユーザーが広告をクリックしたときに呼び出されます。
</ParamField>

<ParamField path="onAdWillPresentFullScreen(_:)" type="void">
  広告画面が表示される直前に呼び出されます。BGMの一時停止などの処理ができます。
</ParamField>

<ParamField path="onAdDidPresentFullScreen(_:)" type="void">
  広告画面が完全に表示された後に呼び出されます。
</ParamField>

<ParamField path="onAdWillDismissFullScreen(_:)" type="void">
  広告画面が閉じる直前に呼び出されます。
</ParamField>

<ParamField path="onAdDidDismissFullScreen(_:)" type="void">
  広告画面が完全に閉じた後に呼び出されます。BGMの再開や次の広告の事前読み込みができます。
</ParamField>

<ParamField path="onAdFailedToShowFullScreen(_:_:)" type="void">
  広告の表示に失敗したときに呼び出されます。エラーコードで失敗原因を確認できます。
</ParamField>

***

## クロージャコールバック

デリゲートの代わりにクロージャベースのコールバックを使用できます。

```swift theme={null}
let rewardedAd = AdropRewardedAd(unitId: "YOUR_UNIT_ID")

// 必須
rewardedAd.onAdReceived = { ad in
    print("広告受信成功")
}
rewardedAd.onAdFailedToReceive = { ad, errorCode in
    print("広告受信失敗: \(errorCode)")
}

// オプション
rewardedAd.onAdImpression = { ad in print("広告表示") }
rewardedAd.onAdClicked = { ad in print("広告クリック") }
rewardedAd.onAdWillPresentFullScreen = { ad in print("広告表示直前") }
rewardedAd.onAdDidPresentFullScreen = { ad in print("広告表示完了") }
rewardedAd.onAdWillDismissFullScreen = { ad in print("広告閉じる直前") }
rewardedAd.onAdDidDismissFullScreen = { ad in print("広告閉じた") }
rewardedAd.onAdFailedToShowFullScreen = { ad, errorCode in print("広告表示失敗: \(errorCode)") }
```

<Note>
  デリゲートとクロージャの両方を設定すると、両方が呼び出されます。
</Note>

***

## 報酬処理

### 報酬コールバック

`show()`メソッドの`userDidEarnRewardHandler`パラメータで報酬コールバックを渡します。

```swift theme={null}
rewardedAd?.show(fromRootViewController: self) { type, amount in
    // type: 報酬タイプ (Int)
    // amount: 報酬数量 (Int)
    print("報酬獲得 - タイプ: \(type), 数量: \(amount)")
}
```

### 報酬タイプと数量

* **type**: Adropコンソールで設定した報酬タイプ (整数値)
* **amount**: Adropコンソールで設定した報酬数量 (整数値)

## サーバーサイド検証

サーバーサイド検証（SSV）を使用すると、サーバーで報酬付与を検証でき、クライアント側の改ざんを防止できます。`AdropServerSideVerificationOptions`を使用して、サーバー間コールバックに含まれるユーザー識別情報とカスタムデータを渡します。

<Note>
  サーバーでSSVコールバックを受信するには、コンソールの\*\*\[管理] > \[連携] > リワード広告SSV\*\*でコールバックURLを登録してください。ここで設定した`userId`と`customData`は、そのURLへ送信される暗号化ペイロードに含まれます。ペイロードの復号と設定方法は[リワード広告SSVガイド](/ja/integrations/ssv)をご参照ください。
</Note>

### SSVの設定

```swift theme={null}
let rewardedAd = AdropRewardedAd(unitId: "YOUR_UNIT_ID")
rewardedAd.delegate = self

// 広告の読み込み前にサーバーサイド検証オプションを設定
let ssvOptions = AdropServerSideVerificationOptions(
    userId: "user_12345",
    customData: "extra_info"
)
rewardedAd.serverSideVerificationOptions = ssvOptions

rewardedAd.load()
```

### AdropServerSideVerificationOptions

| プロパティ        | 型         | 説明                   |
| ------------ | --------- | -------------------- |
| `userId`     | `String?` | サーバーサイド検証用のユーザー識別子   |
| `customData` | `String?` | コールバックに含めるカスタムデータ文字列 |

```swift theme={null}
// パラメータで初期化
let options = AdropServerSideVerificationOptions(
    userId: "user_12345",
    customData: "level_5_reward"
)

// または個別にプロパティを設定
let options = AdropServerSideVerificationOptions()
options.userId = "user_12345"
options.customData = "level_5_reward"

rewardedAd.serverSideVerificationOptions = options
```

<Note>
  `load()`を呼び出す前に`serverSideVerificationOptions`を設定してください。`userId`と`customData`の値は、ユーザーが報酬を獲得したときにサーバーコールバックURLに送信されます。
</Note>

***

## ベストプラクティス

### 1. 広告の事前読み込み

ユーザー体験のために広告を事前に読み込んでおくことをお勧めします。

```swift theme={null}
class GameViewController: UIViewController {
    private var rewardedAd: AdropRewardedAd?

    override func viewDidLoad() {
        super.viewDidLoad()

        // 画面進入時に広告を事前読み込み
        loadRewardedAd()
    }

    private func loadRewardedAd() {
        rewardedAd = AdropRewardedAd(unitId: "YOUR_UNIT_ID")
        rewardedAd?.delegate = self
        rewardedAd?.load()
    }

    private func showRewardedAd() {
        rewardedAd?.show(fromRootViewController: self) { [weak self] type, amount in
            self?.grantReward(type: type, amount: amount)
        }
    }
}

extension GameViewController: AdropRewardedAdDelegate {
    func onAdReceived(_ ad: AdropRewardedAd) {
        print("リワード広告準備完了")
    }

    func onAdFailedToReceive(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode) {
        print("広告読み込み失敗: \(errorCode)")
    }

    func onAdDidDismissFullScreen(_ ad: AdropRewardedAd) {
        // 広告が閉じた後、次の広告を事前読み込み
        loadRewardedAd()
    }
}
```

### 2. 広告準備状態の確認

広告が読み込まれたかを確認し、UIを更新します。

```swift theme={null}
class GameViewController: UIViewController {
    @IBOutlet weak var watchAdButton: UIButton!
    private var rewardedAd: AdropRewardedAd?
    private var isAdReady = false

    private func updateButtonState() {
        watchAdButton.isEnabled = isAdReady
        watchAdButton.alpha = isAdReady ? 1.0 : 0.5
        watchAdButton.setTitle(
            isAdReady ? "動画を見てライフを獲得" : "広告読み込み中...",
            for: .normal
        )
    }
}

extension GameViewController: AdropRewardedAdDelegate {
    func onAdReceived(_ ad: AdropRewardedAd) {
        isAdReady = true
        updateButtonState()
    }

    func onAdFailedToReceive(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode) {
        isAdReady = false
        updateButtonState()
    }

    func onAdDidDismissFullScreen(_ ad: AdropRewardedAd) {
        isAdReady = false
        updateButtonState()

        // 次の広告を読み込み
        rewardedAd?.load()
    }
}
```

### 3. BGM処理

広告表示前後でBGMを適切に制御します。

```swift theme={null}
extension GameViewController: AdropRewardedAdDelegate {
    func onAdReceived(_ ad: AdropRewardedAd) { }
    func onAdFailedToReceive(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode) { }

    func onAdWillPresentFullScreen(_ ad: AdropRewardedAd) {
        // 広告表示直前にBGMを一時停止
        AudioManager.shared.pauseBackgroundMusic()
    }

    func onAdDidDismissFullScreen(_ ad: AdropRewardedAd) {
        // 広告終了後にBGMを再開
        AudioManager.shared.resumeBackgroundMusic()
    }
}
```

### 4. エラー処理

広告読み込み失敗時のリトライロジックを実装します。

```swift theme={null}
extension GameViewController: AdropRewardedAdDelegate {
    func onAdFailedToReceive(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode) {
        print("広告読み込み失敗: \(errorCode)")

        // ネットワークエラーの場合リトライ
        if errorCode == .ERROR_CODE_NETWORK {
            DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { [weak self] in
                self?.rewardedAd?.load()
            }
        }
    }

    func onAdFailedToShowFullScreen(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode) {
        print("広告表示失敗: \(errorCode)")

        // ユーザーに通知
        showAlert(message: "広告を表示できません。後でもう一度お試しください。")
    }
}
```

### 5. メモリ管理

ViewControllerが解放されるとき、広告オブジェクトもクリーンアップします。

```swift theme={null}
class GameViewController: UIViewController {
    private var rewardedAd: AdropRewardedAd?

    deinit {
        rewardedAd?.delegate = nil
        rewardedAd = nil
    }
}
```

***

## テスト

### テストユニットID

開発中は必ずテストユニットIDを使用してください。

```swift theme={null}
// 開発環境
let rewardedAd = AdropRewardedAd(unitId: "PUBLIC_TEST_UNIT_ID_REWARDED")

// 本番環境
let rewardedAd = AdropRewardedAd(unitId: "YOUR_PRODUCTION_UNIT_ID")
```

<Warning>
  実際のユニットIDでテストすると、無効なトラフィックと見なされ、アカウントが停止される可能性があります。必ずテストユニットIDを使用してください。
</Warning>

### 環境別ユニットID管理

ビルド設定に応じてユニットIDを自動的に切り替える方法です。

```swift theme={null}
enum AdUnitID {
    static var rewarded: String {
        #if DEBUG
        return "PUBLIC_TEST_UNIT_ID_REWARDED"
        #else
        return "YOUR_PRODUCTION_UNIT_ID"
        #endif
    }
}

// 使用方法
let rewardedAd = AdropRewardedAd(unitId: AdUnitID.rewarded)
```

***

## 関連ドキュメント

<CardGroup cols={2}>
  <Card title="インタースティシャル広告" href="/ja/sdk/ios/interstitial" icon="rectangle-ad">
    インタースティシャル広告実装ガイド
  </Card>

  <Card title="エラーコード" href="/ja/sdk/ios/reference#error-codes" icon="triangle-exclamation">
    AdropErrorCodeリファレンス
  </Card>

  <Card title="サンプルプロジェクト" href="/ja/sdk/ios/examples" icon="code">
    全体のサンプルコードを見る
  </Card>
</CardGroup>
