Hiding or Showing Google Ad View in Swift with Sprite Kit - swift

In my game I would like there to be an Google Banner View in the main menu scene and game over scene. Here's what I have in the GameViewController:
override func viewWillLayoutSubviews(){
super.viewWillLayoutSubviews()
let skView = self.view as! SKView
googleBannerView = GADBannerView(adSize: kGADAdSizeSmartBannerPortrait)
googleBannerView.adUnitID = "ca-app-pub-3940256099942544/2934735716"
googleBannerView.rootViewController = self
var request: GADRequest = GADRequest()
googleBannerView.loadRequest(request)
googleBannerView.frame = CGRectMake(0, skView.bounds.height - googleBannerView.frame.size.height, googleBannerView.frame.size.width, googleBannerView.frame.size.height)
self.view.addSubview(googleBannerView!)
if skView.scene == nil{
let mainMenuScene = MainMenuScene(size: skView.bounds.size)
mainMenuScene.scaleMode = SKSceneScaleMode.AspectFill
mainMenuScene.backgroundColor = UIColor.whiteColor()
skView.presentScene(mainMenuScene)
}
}
func showBanner(){
if googleBannerView != nil{
self.googleBannerView!.hidden = false
var request: GADRequest = GADRequest()
self.googleBannerView.loadRequest(request)
}
}
func hideBanner(){
println("hideBanner() called")
self.googleBannerView.hidden = true
}
In the GameScene I have this:
override init(size: CGSize) {
super.init(size: size)
let gameViewController = GameViewController()
gameViewController.hideBanner()
When I run this it starts up fine, but when I press play it crashes and says: fatal error: unexpectedly found nil while unwrapping an Optional value. This doesn't make much sense because it can't be nil because I know there is an ad banner, right? What I am doing wrong. Thank you in advance.
-Vinny

There could possibly be an error in your show banner function. Try taking out the exclamation in:
self.googleBannerView!.hidden = false
If the banner view is not nil, then it doesn't need to unwrapped

Related

Why is my interstitial Ad Producing an error?

I am trying to show an interstitial Ad whenever the game over scene is displayed and I am just getting in error. I have searched for answers but they produced the same result. I am using the Notification Center method and it is not working. My banner view is working just fine though.
I am getting this error:
CoreAnimation: [EAGLContext renderbufferStorage:fromDrawable:] was
called from a non-main thread in an implicit transaction! Note that
this may be unsafe without an explicit CATransaction or a call to
[CATransaction flush]. 2017-10-01 09:28:00.631015-0500
Split[868:273850] CoreAnimation: [EAGLContext
renderbufferStorage:fromDrawable:] was called from a non-main thread
in an implicit transaction! Note that this may be unsafe without an
explicit CATransaction or a call to [CATransaction flush].
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.loadAndShow), name: NSNotification.Name(rawValue: "loadAndShow"), object: nil)
let request = GADRequest()
request.testDevices = [kGADSimulatorID, "49d92844bdbdf74dac25e343b377744f"]
bannerView = GADBannerView(adSize: kGADAdSizeSmartBannerPortrait)
self.view?.addSubview(bannerView)
bannerView.adUnitID = "ca-app-pub-9408273734198014/6590781864"
bannerView.rootViewController = self
bannerView.load(GADRequest())
bannerView.delegate = self
interstitialView = GADInterstitial(adUnitID: "ca-app-pub-9408273734198014/7472368678")
interstitialView.load(request)
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "StartScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
#objc func loadAndShow() {
interstitialView = GADInterstitial()
let request = GADRequest()
interstitialView.setAdUnitID("ca-app-pub-9408273734198014/7472368678")
interstitialView.delegate = self
interstitialView.load(request)
}
func showInterstitial(ad: GADInterstitial!) {
if (self.interstitialView.isReady) {
interstitialView.present(fromRootViewController: self)
}
}
So, as mentioned in error, you are calling interstitialView.present(fromRootViewController: self) in non main thread, it is called from a thread that was used to load the interstitial.
To fix it just present it in main queue.
DispatchQueue.main.async {
self.interstitialView.present(fromRootViewController: self)
}

Crashes with completion handler in GameViewController

Want to preload textureAtlases before game starts, so I decided to put scene starter code into completion handler, but for some reason app crashes. Here is my code:
import UIKit
import SpriteKit
import GameplayKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let gpuAtlas = SKTextureAtlas(named: "GP")
let ppAtlas = SKTextureAtlas(named: "PP")
SKTextureAtlas.preloadTextureAtlases([gpuAtlas, ppAtlas]) {
if let view = self.view as? SKView {
let scene = GameScene(size: self.view.bounds.size)
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
override var prefersStatusBarHidden: Bool {
return true
}
}
If I remove preload texture atlas method everything will work as usual, but I want preload textures to have a change to get rid of first keyframe freeze because of loading textures in cache.
Error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Update:
It crashes on GameScene.swift file in method didSimulatePhysics.
What I do wrong guys?
Try this (use main thread for manipulation with view and weak self):
SKTextureAtlas.preloadTextureAtlases([gpuAtlas, ppAtlas]) { [weak self] in
guard let gameView = self?.view as? SKView else { return }
DispatchQueue.main.async {
let scene = GameScene(size: gameView.bounds.size)
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
gameView.presentScene(scene)
gameView.ignoresSiblingOrder = true
gameView.showsFPS = true
gameView.showsNodeCount = true
}
}
Did you check if scene is optional? If so:
guard let scene = GameScene(size: gameView.bounds.size) else { return }
This
let gpuAtlas = SKTextureAtlas(named: "GP")
let ppAtlas = SKTextureAtlas(named: "PP")
SKTextureAtlas.preloadTextureAtlases([GP, PP]) {}
should not compile - the variables are named gpuAtlas and ppAtlas and not GP and PP.

"unexpectedly found nil while unwrapping an Optional value" when calling from another scene

I created the GameScene first and it works, now I'm trying to add a MainMenu scene which also works except when I hit the button(sprite) to start game, then I get the error above.
The GameViewController is just stock apple code and it starts the app with the MainMenu scene, but I've tested the app just running GameScene in GameViewController and I don't get any error but then i'll have no mainMenu
Within GameScene:
var UpperLeft = SKSpriteNode()
var BottomRight = SKSpriteNode()
var UpperRight = SKSpriteNode()
var BottomLeft = SKSpriteNode()
var Ball = SKSpriteNode()
the error is with each one i'm forcing to unwrap ↓
override func didMove(to view: SKView) {
scoreLabel = self.childNode(withName:"scoreLabel") as! SKLabelNode
UpperLeft = self.childNode(withName:"UpperLeft") as! SKSpriteNode
BottomRight = self.childNode(withName:"BottomRight") as! SKSpriteNode
UpperRight = self.childNode(withName:"UpperRight") as! SKSpriteNode
BottomLeft = self.childNode(withName:"BottomLeft") as! SKSpriteNode
Ball = self.childNode(withName:"Ball") as! SKSpriteNode
The error is with each one i'm forcing to unwrap ↑
Within MainMenu:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if let location = touches.first?.location(in: self) {
let touchedNode = atPoint(location)
if touchedNode.name == "StartGame" {
let transition = SKTransition.reveal(with: .down, duration: 1.0)
let nextScene = GameScene(size: scene!.size)
nextScene.scaleMode = .aspectFill
scene?.view?.presentScene(nextScene, transition: transition)
}
}
}
Within GameViewController:
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "mainMenu") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
view.ignoresSiblingOrder = true
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
I understand that the error comes from the as! forcing the unwrap but I don't see how calling the GameScene from the mainMenu scene causes this to be an issue? Because I did create an object for each unwrap so they should not be nil?
The problem is in the way you initialize GameScene
use the following code:
let nextScene = GameScene(fileNamed: "GameScene")
instead of the one used in MainMenu:
let nextScene = GameScene(size: scene!.size)
when you use size to initialize a scene, it doesn't recognize the .sks file of the scene, so all your nodes which defines in .sks file become nil and that's why you get error while unwrapping them.

Use SKSpriteNode as button to transition from GameScene to UITabBarController

I have a UITabBarController that has two items, the first one being a calendar which is just a UIWebView. The second item brings in a SpriteKit game scene and hides the tab bar (The game needs to be full screen).
I have an SKSpriteNode image that when tapped, I'd like the view to change back to the calendar view (the first tab bar item). I've searched for a way to do this and have tried various suggestions I've found here on StackOverflow, but nothing is working. The app crashes with the error
"Unexpectedly found nil while unwrapping an optional"
I understand what that error means, just not why I'm getting it. Here is the relevant code (part of which I got from here while trying to figure this out):
In GameScene.swift:
weak var viewController: GameViewController!
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch:UITouch = touches.first! as UITouch
let positionInScene = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(positionInScene)
if let name = touchedNode.name {
if name == "exit" {
self.viewController.gameOver()
self.removeFromParent()
self.view?.presentScene(nil)
}
}
}
In GameViewController.swift:
var currentGame: GameScene!
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.tabBar.hidden = true
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
scene.viewController = self
skView.presentScene(scene)
currentGame = scene
}
}
func gameOver() {
performSegueWithIdentifier("backToMain", sender: nil)
}
This is the line giving the error:
self.viewController.gameOver()
Your code is correct; I think the likely problem is that you are presenting more than one scene without transferring the value of viewController. To track this problem down, use the Find navigator (Cmd+3) to search for presentScene then ensure each call is after you have transferred the value of the viewController property across to the new scene.
I think just remove the 'weak' from the declaration of viewController. You can set a breakpoint on the line giving the error and see if viewController has a value (try po self.viewController in the Xcode console).

Prioritizing iAd over adMob SpriteKit / Swift

I integrated both iAd and adMob into my game, with adMob code only running when iAd fails to load. If the iAd does not fail loading the first ad, it works fine. Admob works like it should when iAd fails to load. However, when iAd fails but then loads the next ad, the adMob banner still exists and the iAd is not shown. How do I make it so that iAd loads even when adMob already is. Also, the iAd banner loads fine after failing to load when the adMob code is commented out. On an unrelated note, how do change the fill rate of adMob ads in the simulator? Thanks. This is my code in my GameViewController (iAd is called in gameScene):
import UIKit
import SpriteKit
import iAd
import GoogleMobileAds
class GameViewController: UIViewController, ADBannerViewDelegate {
var SH = UIScreen.mainScreen().bounds.height
let transition = SKTransition.fadeWithDuration(1)
var UIiAd: ADBannerView = ADBannerView()
var googleBannerView: GADBannerView!
override func viewWillAppear(animated: Bool) {
/* var BV = UIiAd.bounds.height
UIiAd.delegate = self
UIiAd.frame = CGRectMake(0, SH + BV, 0, 0)
self.view.addSubview(UIiAd) */
UIiAd.setTranslatesAutoresizingMaskIntoConstraints(false)
UIiAd.delegate = self
self.view.addSubview(UIiAd)
let viewsDictionary = ["bannerView":UIiAd]
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[bannerView]|", options: .allZeros, metrics: nil, views: viewsDictionary))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[bannerView]|", options: .allZeros, metrics: nil, views: viewsDictionary))
}
override func viewWillDisappear(animated: Bool) {
UIiAd.delegate = nil
UIiAd.removeFromSuperview()
}
func bannerViewDidLoadAd(banner: ADBannerView!) {
var BV = UIiAd.bounds.height
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(1) // Time it takes the animation to complete
UIiAd.alpha = 1 // Fade in the animation
UIView.commitAnimations()
println("iAd work")
}
func bannerView(banner: ADBannerView!, didFailToReceiveAdWithError error: NSError!) {
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(1)
UIiAd.alpha = 0
UIView.commitAnimations()
googleBannerView = GADBannerView(adSize: kGADAdSizeSmartBannerLandscape)
googleBannerView.adUnitID = "ca-app-pub-3940256099942544/2934735716"
googleBannerView.rootViewController = self
var request: GADRequest = GADRequest()
googleBannerView.loadRequest(request)
googleBannerView.frame = CGRectMake(0, view.bounds.height - googleBannerView.frame.size.height, googleBannerView.frame.size.width, googleBannerView.frame.size.height)
self.view.addSubview(googleBannerView!)
println("iAd fail")
}
func showBannerAd() {
UIiAd.hidden = false
var BV = UIiAd.bounds.height
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(1) // Time it takes the animation to complete
UIiAd.frame = CGRectMake(0, SH - BV, 2048, 0) // End position of the animation
UIView.commitAnimations()
}
func hideBannerAd() {
UIiAd.hidden = true
var BV = UIiAd.bounds.height
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(1) // Time it takes the animation to complete
UIiAd.frame = CGRectMake(0, SH + BV, 0, 0) // End position of the animation
UIView.commitAnimations()
}
override func viewDidLoad() {
super.viewDidLoad()
self.UIiAd.hidden = true
self.UIiAd.alpha = 0
NSNotificationCenter.defaultCenter().addObserver(self, selector: "hideBannerAd", name: "hideadsID", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "showBannerAd", name: "showadsID", object: nil)
let scene = GameScene(size: CGSize(width: 2048, height: 1356))
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
scene.scaleMode = SKSceneScaleMode.AspectFill
skView.presentScene(scene)
}
func bannerViewActionShouldBegin(banner: ADBannerView!, willLeaveApplication willLeave: Bool) -> Bool {
println("Clicked")
// let skView: SKView = self.view as! SKView
// skView.scene!.paused = true
return true
}
/* func bannerViewActionDidFinish(banner: ADBannerView!) {
let skView: SKView = self.view as! SKView
skView.scene!.paused = false
} */
override func prefersStatusBarHidden() -> Bool {
return true
}
}
You're not hiding your AdMob banner when iAd receives an ad in your bannerViewDidLoadAd(banner: ADBannerView!). So, your iAd banner is actually being displayed behind your AdMob banner.
Alternatively, you could create both your iAd and AdMob banners once in your viewDidLoad and hide or show them depending on if iAd receives an ad or not. Your iAd delegate methods would end up looking similar to this:
func bannerViewDidLoadAd(banner: ADBannerView!) {
UIView.beginAnimations(nil, context: nil)
// Show iAd
UIiAd.alpha = 1.0
// Hide AdMob
googleBannerView.alpha = 0.0
UIView.commitAnimations()
}
func bannerView(banner: ADBannerView!, didFailToReceiveAdWithError error: NSError!) {
UIView.beginAnimations(nil, context: nil)
// Hide iAd
UIiAd.alpha = 0.0
// Show AdMob
googleBannerView.alpha = 1.0
UIView.commitAnimations()
}
Check my answer here for a complete implementation.
I made an ad helper for swift, why don't you check it out, it does exactly what you want. It was primarily made for SpriteKit.
https://github.com/crashoverride777/Swift-iAds-and-AdMob-Helper