3

I have a SwiftUI view that I want to open a rewarded ad from the Google Mobile Ads SDK when I press a button. The instructions for loading the ads (https://developers.google.com/admob/ios/rewarded-ads#create_rewarded_ad) are in UIKit, and I'm struggling to use them in my SwiftUI app. Is there a way to load the ads using SwiftUI, or if I use UIKit, how do I integrate it into SwiftUI?

This is the SwiftUI parent view:

struct AdMenu: View {

    var body: some View {
   NavigationView {
       NavigationLink(destination: Ads())
        {
            Text("Watch Ad")
        }
    }
}
}

I don't know UIKit, but I think this is the code I want to use in SwiftUI:

    class ViewController: UIViewController, GADRewardedAdDelegate {

    var rewardedAd: GADRewardedAd?

var adRequestInProgress = false

  @IBAction func doSomething(sender: UIButton) {
    if rewardedAd?.isReady == true {
       rewardedAd?.present(fromRootViewController: self, delegate:self)
    }else {
      let alert = UIAlertController(
        title: "Rewarded video not ready",
        message: "The rewarded video didn't finish loading or failed to load",
        preferredStyle: .alert)
      let alertAction = UIAlertAction(
        title: "OK",
        style: .cancel,
        handler: { [weak self] action in
            // redirect to AdMenu SwiftUI view somehow?
        })
      alert.addAction(alertAction)
      self.present(alert, animated: true, completion: nil)
    }
}

func createAndLoadRewardedAd() {
    rewardedAd = GADRewardedAd(adUnitID: "ca-app-pub-3940256099942544/1712485313")
    adRequestInProgress = true
    rewardedAd?.load(GADRequest()) { error in
      self.adRequestInProgress = false
      if let error = error {
        print("Loading failed: \(error)")
      } else {
        print("Loading Succeeded")
      }
    }
  return rewardedAd
}

// Tells the delegate that the user earned a reward
func rewardedAd(_ rewardedAd: GADRewardedAd, userDidEarn reward: GADAdReward) {
  print("Reward received with currency: \(reward.type), amount \(reward.amount).")
}
// Tells the delegate that the rewarded ad was presented
func rewardedAdDidPresent(_ rewardedAd: GADRewardedAd) {
  print("Rewarded ad presented.")
}
// Tells the delegate that the rewarded ad was dismissed
func rewardedAdDidDismiss(_ rewardedAd: GADRewardedAd) {
  print("Rewarded ad dismissed.")
}
// Tells the delegate that the rewarded ad failed to present
func rewardedAd(_ rewardedAd: GADRewardedAd, didFailToPresentWithError error: Error) {
    rewardedAd = createAndLoadRewardedAd()
    print("Rewarded ad failed to present.")
}

    override func viewDidLoad() {
        super.viewDidLoad()
    if !adRequestInProgress && !(rewardedAd?.isReady ?? false) {
    rewardedAd = createAndLoadRewardedAd()
}
Mira
  • 131
  • 1
  • 9

2 Answers2

3

I have used a very simple approach. I have a delegate class which loads the rewarded Ad and informs the view that its loaded, hence the view presents it. When user watches the full Ad, same delegate gets the success callback and it informs the view about it.

The code for delegate looks like:-

class RewardedAdDelegate: NSObject, GADRewardedAdDelegate, ObservableObject {
@Published var adLoaded: Bool = false
@Published var adFullyWatched: Bool = false

var rewardedAd: GADRewardedAd? = nil

func loadAd() {
    rewardedAd = GADRewardedAd(adUnitID: "ca-app-pub-3940256099942544/1712485313")
        rewardedAd!.load(GADRequest()) { error in
          if error != nil {
            self.adLoaded = false
          } else {
            self.adLoaded = true
          }
        }
}

/// Tells the delegate that the user earned a reward.
func rewardedAd(_ rewardedAd: GADRewardedAd, userDidEarn reward: GADAdReward) {
    adFullyWatched = true
}

/// Tells the delegate that the rewarded ad was presented.
func rewardedAdDidPresent(_ rewardedAd: GADRewardedAd) {
     self.adLoaded = false
}

/// Tells the delegate that the rewarded ad was dismissed.
func rewardedAdDidDismiss(_ rewardedAd: GADRewardedAd) {}

/// Tells the delegate that the rewarded ad failed to present.
func rewardedAd(_ rewardedAd: GADRewardedAd, didFailToPresentWithError error: Error) {}
}

Now you need a view to initiate and present the RewardedAd:-

struct RewardedAd: View {
@ObservedObject var adDelegate = RewardedAdDelegate()

var body: some View {
    if adDelegate.adLoaded && !adDelegate.adFullyWatched {
        let root = UIApplication.shared.windows.first?.rootViewController
        self.adDelegate.rewardedAd!.present(fromRootViewController: root!, delegate: adDelegate)
    }
    
    return Text("Load ad").onTapGesture {
        self.adDelegate.loadAd()
    }
}
}

Explanation:- In the above view when user taps on Load Ad, we initiate the loading and then the delegate updates the Published Boolean. This informs our view that ad is Loaded and we call:-

let root = UIApplication.shared.windows.first?.rootViewController self.adDelegate.rewardedAd!.present(fromRootViewController: root!, delegate: adDelegate)

The Ad is now playing on screen and if user watches is completely, the delegate will get a success callback and it'll update another Published Boolean. This will inform us that we need to reward the user now (you can add your way to handle/reward the user).

Nimantha
  • 6,405
  • 6
  • 28
  • 69
mohit kejriwal
  • 1,755
  • 1
  • 10
  • 19
  • Do you know how to tell when the user tapped the X after getting the reward? I get adFullyWatched=true but then my game continues going and the user doesn't see the game now going along in the background, until they actually close the ad. Is there any callback? rewardedAdDidDismiss() is only called if they closed it too early. – Curtis Jan 20 '21 at 07:37
0

I figured out a way how to nicely translate the objective-c code from the AdMob page to something you can easily use in SwiftUI.


final class RewardedAd {
    private let rewardId = "ca-app-pub-3940256099942544/1712485313" // TODO: replace this with your own Ad ID
    
    var rewardedAd: GADRewardedAd?
    
    init() {
        load()
    }
    
    func load(){
        let request = GADRequest()
        // add extras here to the request, for example, for not presonalized Ads
        GADRewardedAd.load(withAdUnitID: rewardId, request: request, completionHandler: {rewardedAd, error in
            if error != nil {
                // loading the rewarded Ad failed :( 
                return
            }
            self.rewardedAd = rewardedAd
        })
    }
    
    func showAd(rewardFunction: @escaping () -> Void) -> Bool {
        guard let rewardedAd = rewardedAd else {
            return false
        }
        
        guard let root = UIApplication.shared.keyWindowPresentedController else {
            return false
        }
        rewardedAd.present(fromRootViewController: root, userDidEarnRewardHandler: rewardFunction)
        return true
    }
}

I also added an extension to get the root view easier:

extension UIApplication {
    
    var keyWindow: UIWindow? {
        return UIApplication.shared.connectedScenes
            .filter { $0.activationState == .foregroundActive }
            .first(where: { $0 is UIWindowScene })
            .flatMap({ $0 as? UIWindowScene })?.windows
            .first(where: \.isKeyWindow)
    }
    
    var keyWindowPresentedController: UIViewController? {
        var viewController = self.keyWindow?.rootViewController
        
        if let presentedController = viewController as? UITabBarController {
            viewController = presentedController.selectedViewController
        }
        
        while let presentedController = viewController?.presentedViewController {
            if let presentedController = presentedController as? UITabBarController {
                viewController = presentedController.selectedViewController
            } else {
                viewController = presentedController
            }
        }
        return viewController
    }
}

If you add that to your project you can just call it like this in your SwiftUI code:

var rewardAd: RewardedAd
        
    init(){
        self.rewardAd = RewardedAd()
        rewardAd.load()
    }
    
    var body: some View {
        Button(action: {
            self.rewardAd.showAd(rewardFunction: {
              // TODO: give the user a reward for watching
              self.collectedDiamonds += 1
            })
        }) {
            Text("Watch Ad - Earn ")
                .foregroundColor(.white)
                .padding()
        }
        .background(Capsule().fill(Color.blue))
    }
Nimantha
  • 6,405
  • 6
  • 28
  • 69
Ix76
  • 86
  • 7