SwiftUIでリワード広告(UIViewControllerRepresentable使用)

タイトルの通りリワード広告の実装です。
少々特殊なprj構成でポカしたのでUIViewControllerRepresentable使いました。本来は要らないです。
次バージョンで直す予定なので記録用に残しときます。

多分あまり参考にはならないです。
作り直したら別な記事を書く予定です。

今回のprjはSwiftUIで作成、どうしても必要になったので後からAppDelegateとSceneDelegate追加のパターンです。

RewardedAdDelegate

まずはリワード広告用のデリゲートクラスからです。
GADFullScreenContentDelegateを使います。
また広告のロード完了で表示を変えるのでObservableObjectも使用します。

import GoogleMobileAds

class RewardedAdDelegate: NSObject, GADFullScreenContentDelegate, ObservableObject {
    static var instance = RewardedAdDelegate()
    var rewardedAd: GADRewardedAd?
    
    @Published var adLoaded = false
    @Published var isRewarded = false

    override init() {
        super.init()
        
        let request = GADRequest()
        
        GADRewardedAd.load(withAdUnitID: "ca-app-pub-3940256099942544/1712485313"
                           ,request: request ){ (rewardedAd, error) in
            if let error = error {
                print(error.localizedDescription)
                return
            }
            self.rewardedAd = rewardedAd
            self.rewardedAd?.fullScreenContentDelegate = self
            self.adLoaded = true
        }
    }
    
    // 広告を表示
    func showRewardedAd() {
        guard let root = UIApplication.shared.windows.first?.rootViewController else {
            return
        }
        self.showRewardedAd(viewController: root)
    }

    // 広告を表示
    func showRewardedAd(viewController:UIViewController) {
        if let rewardedAd = self.rewardedAd {
            rewardedAd.present(fromRootViewController: viewController,
                       userDidEarnRewardHandler: {

                        self.isRewarded = true
                       }
            )
        } else {
            print("Ad wasn't ready")
        }
    }
    
    //広告が表示された時
    func adDidPresentFullScreenContent(_ ad: GADFullScreenPresentingAd) {
    }
    
    //広告を閉じた時
    func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
        // 報酬獲得前に止めた場合インスタンスを作り直す
        if !self.isRewarded{
            RewardedAdDelegate.instance = RewardedAdDelegate()
        }
    }
    
    //表示に失敗した時
    func ad(_ ad: GADFullScreenPresentingAd ,didFailToPresentFullScreenContentWithError error: Error) {
        print(\(error.localizedDescription))
    }
}

それでは詳しく
イニシャライザから

    override init() {
        super.init()
        
        let request = GADRequest()
        
        GADRewardedAd.load(withAdUnitID: "ca-app-pub-3940256099942544/1712485313"
                           ,request: request ){ (rewardedAd, error) in
            if let error = error {
                print(error.localizedDescription)
                return
            }
            self.rewardedAd = rewardedAd
            self.rewardedAd?.fullScreenContentDelegate = self
            self.adLoaded = true
        }
    }

初期化時に広告をロードします。
ロード完了時にクロージャが実行されるので成功したら
GADRewardedAdインスタンスの保持、Delegateのセット、adLoadedフラグを変更します。

次に広告の表示

// 広告を表示
    func showRewardedAd() {
        guard let root = UIApplication.shared.windows.first?.rootViewController else {
            return
        }
        self.showRewardedAd(viewController: root)
    }

    // 広告を表示
    func showRewardedAd(viewController:UIViewController) {
        if let rewardedAd = self.rewardedAd {
            rewardedAd.present(fromRootViewController: viewController,
                       userDidEarnRewardHandler: {

                        self.isRewarded = true
                       }
            )
        } else {
            print("Ad wasn't ready")
        }
    }

広告表示のpresentでviewControllerを要求されます。
単純なSwiftUIアプリであればUIApplication.shared.windows.first?.rootViewControllerでOKの筈です。
今回はそのまま実行すると音だけ出て動画が見えません。

SceneDelegateがあるのでそこで生成したViewControllerが必要でした。
そのため2パターン用意しました。
気づかずにやったので後でUIViewControllerRepresentableで広告用にViewControllerを作ります。

広告が最後まで再生された際にフラグを立ててます。後で使います。

次にDelegateのメソッドです

    //広告が表示された時
    func adDidPresentFullScreenContent(_ ad: GADFullScreenPresentingAd) {
    }
    
    //広告を閉じた時
    func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
        // 報酬獲得前に止めた場合インスタンスを作り直す
        if !self.isRewarded{
            RewardedAdDelegate.instance = RewardedAdDelegate()
        }
    }
    
    //表示に失敗した時
    func ad(_ ad: GADFullScreenPresentingAd ,didFailToPresentFullScreenContentWithError error: Error) {
        print(\(error.localizedDescription))
    }

報酬獲得前に広告が閉じられた時に再度再生が可能なようにインスタンスを作り直しています。
GADRewardedAdのloadの関係ですね。
initでloadしてるので取り敢えず再生成でこのあたりをもう一度やっています。

RewardAdView

GADRewardedAdに渡すViewControllerの為に専用のViewController作りました。
ボタンだけ置いてあります。

struct RewardAdViewWrapper:UIViewControllerRepresentable {
    
    typealias UIViewControllerType = RewardAdViewController
 
    let size:CGSize
    
    func makeUIViewController(context: Context) -> RewardAdViewController {
        let controller = RewardAdViewController()
        controller.buttonSize = self.size
        return controller
    }
    
    func updateUIViewController(_ uiViewController: RewardAdViewController, context: Context) {
        
    }
    
}


class RewardAdViewController:UIViewController{
    
    let reward = RewardedAdDelegate.instance
    var buttonSize:CGSize!
    
    override func viewDidLoad() {
        // ボタンのインスタンス生成
        let button = UIButton()
        button.titleLabel?.font =  UIFont.systemFont(ofSize: 30)
        button.backgroundColor = UIColor.blue
        button.setTitle("広告を再生する", for:UIControl.State.normal)
        button.frame = CGRect(x: 0 ,y: 0
                              ,width: self.buttonSize.width
                              ,height: self.buttonSize.height)
        button.addTarget(self ,action: #selector(buttonTapped) ,for: .touchUpInside)
        
        self.view.addSubview(button)
    }
    
    @objc func buttonTapped(_ sender : Any) {
        self.reward.showRewardedAd(viewController: self)
    }
}

ボタンタップで広告表示メソッドを呼びます。
ここでselfを渡してしまうのでrootViewControllerがどうなっていようと関係ありません。

ちなみにここでボタンサイズを渡していますが、
これはSwiftUIで配置しやすくする為です。

UIViewControllerRepresentableだとそのままだと画面サイズのViewが配置されますし、
frame指定しても画面サイズ基準でAutoLayoutが動作します。

なのでRewardAdViewWrapperにframeでサイズ指定をし、
ボタンを同じサイズで原点に置いてボタン部分だけのViewとして扱います。

コメント

  1. […] […]

タイトルとURLをコピーしました