Search code examples
iosswiftnulladmobinterstitial

Swift Admob Interstitial Error


This is pretty long but it's probably a really easy fix. Most of this is code, just skip to the bottom and there'll be a tl;dr

Hey, so I've followed about 10 tutorials on getting Admob interstitials to work in my Swift Spritekit game. Except all the tutorials I've seen use an IBAction with a button. Currently I would like the ads to pop up every once in a while on the menu. (just to begin with, I might change it later). This all works perfectly, but once I hit the play button (going to a different scene) and then return to the menu scene. (GameScene) The interstitial function no longer happens. There is no error, it just doesn't do it any more until the app is completely closed and re-opened.

Here's the code.

In my GameViewController class I have:

var interstital: GADInterstitial!

And in the GameViewController did load I have:

if let scene = GameScene(fileNamed:"GameScene") {
        let skView = self.view as? SKView
        skView!.ignoresSiblingOrder = false
        scene.scaleMode = .AspectFill

        scene.viewController = self
        skView!.presentScene(scene)

        interstital = GADInterstitial(adUnitID: "ca-app-pub-9776687746813194/9687097060")

        let request = GADRequest()
        interstital.loadRequest(request)

Also in the GameViewController class I have these functions:

func ShowAd() {
    print("doing the showadfunc now")

    if (interstital.isReady) {

        print("there is an inter ready")

        interstital.presentFromRootViewController(self)
        interstital = CreateAd()

    }
    else{
        print("The interstitial wasn't ready.")
    }
}

func CreateAd() -> GADInterstitial {

    let interstital = GADInterstitial(adUnitID: "ca-app-pub-9776687748683194/9687247060")
    interstital.loadRequest(GADRequest())
    return interstital

}

In my GameScene's (The menu) class I have:

var viewController: GameViewController!

And in my GameScene's class I have this function for displaying an interstitial ad.

func adThing(){
    //viewController?.ShowAd()

    print("starting the ad function")
    let waitForInterstitial = SKAction.waitForDuration(15.0)
    let waitForInterstitialShort = SKAction.waitForDuration(3.0)
    let showInterstitialAd = SKAction.runBlock({self.viewController?.ShowAd(); print("the function has been called..")})
    let waitThenShowInterstitial = SKAction.sequence([showInterstitialAd,waitForInterstitial])
    let waitThenShowInterStitialForever = SKAction.repeatActionForever(waitThenShowInterstitial)
    //self.runAction(waitThenShowInterStitialForever)
    self.runAction(waitForInterstitialShort, completion: {
        print("its waited 3 seconds and will now start the ad thingo")
        self.runAction(waitThenShowInterStitialForever)
    })
    print("done")

}

So, I call the ShowAd function (which is located in gameviewcontroller) in my GameScene every 20 seconds, (I have confirmed it at least thinks it is calling the function.) It works perfectly when the app is opened to begin with. But when I change scene and then return to the menu scene (GameScene), the Prints in my GameScene function keep happening, and my code thinks that it is calling the ShowAd function, but it seems that nothing is happening, no interstitials pop up, not even the prints that are in the ShowAd function in the GameViewController.

So it seems like after I change scenes and return, my game forgets what the GameViewController is. If I refer to the GameViewController without a '?' it will crash my game with the error "unexpectedly found nil while unwrapping an optional." So after changing scenes, maybe GameViewController becomes nil? How can I fix this.

Please help me!!! (I'm new to programming in case you can't tell. I know it must be tempting to make a snide comment about my code, it is really bare bones and not that good at the moment.)

Thank you so much if you can help me, and wow.. thanks for reading all this.. ;)


Solution

  • You problem most likely is that once you transition to a new scene the viewController property is set to nil hence a crash if you use !.

    In general its not the best practice to reference the viewController in your SKScenes, you should use Delegation or NSNotification Center.

    1) Notification Center (easiest way)

    In your ViewController where you have the function to show ads add a NSNotificationCenter observer in ViewDidLoad

     NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(showAd), name: "ShowInterAdKey", object: nil)
    

    (Selector is the function to call and name is the key to reference this observer)

    Than in your SKScenes when you want to show the ad you simply post the notification

     NSNotificationCenter.defaultCenter().postNotificationName("ShowInterAdKey", object: nil)
    

    2) For delegation examples check out these articles.

    http://stephenradford.me/creating-a-delegate-in-swift/

    https://www.natashatherobot.com/ios-weak-delegates-swift/

    3) I also noticed that you are not preloading the ads before you show them, which is not very efficient and can cause delays showing ads.

    You should call

      interstital = CreateAd()
    

    in viewDidLoad so it will load the first ad ASAP. Than you should use the adMob delegate method

     func interstitialDidDismissScreen(ad: GADInterstitial!) {
         // load next ad
         interstital = CreateAd()
    

    to immediately load the next ad once the current one is closed.

    To use the delegates you can create an extension in ViewController

      extension MyViewController: GADInterstitialDelegate {
    
    func interstitialDidReceiveAd(ad: GADInterstitial!) {
        print("AdMob interstitial did receive ad from: \(ad.adNetworkClassName)")
    }
    
    func interstitialWillPresentScreen(ad: GADInterstitial!) {
        print("AdMob interstitial will present")
        // pause game/app
    }
    
    func interstitialWillDismissScreen(ad: GADInterstitial!) {
        print("AdMob interstitial about to be closed")
    }
    
    func interstitialDidDismissScreen(ad: GADInterstitial!) {
        print("AdMob interstitial closed, reloading...")
        interstitial = createAd()
    }
    
    func interstitialWillLeaveApplication(ad: GADInterstitial!) {
        print("AdMob interstitial will leave application")
        // pause game/app
    }
    
    func interstitialDidFailToPresentScreen(ad: GADInterstitial!) {
        print("AdMob interstitial did fail to present")
    }
    
    func interstitial(ad: GADInterstitial!, didFailToReceiveAdWithError error: GADRequestError!) {
        print(error.localizedDescription)
       }
    }
    

    To make sure these get called go to your createAd function and add this line before returning the ad

     let interstitial = ...
     interstitial.delegate = self
    

    Finally your showAd method should look like this

      func showAd() {
         print("doing the showadfunc now")
    
        if (interstital.isReady) {
            print("there is an inter ready")
            interstital.presentFromRootViewController(self)
        } else{
            print("The interstitial wasn't ready, reloading again.")
            interstital = CreateAd()
    
        }
     }
    

    4) You can also check out my helper on gitHub if you are looking for a more reusable solution.

    https://github.com/crashoverride777/Swift-AdMob-AppLovinTVOS-CustomAds-Helpers

    As a general tip you should also follow the swift naming conventions, your func and properties should start with small letters. Only classes, structs, enums and protocols should start with capital letters. It makes your code harder to read for yourself and on stackoverflow because it is marked blue but shouldn't. You are sometimes doing and sometimes not.

    Hope this helps