I'm currently trying to implement IAP into my SKScene like this Swift Sprite Kit In App Purchase
but I'm having a problem figuring out how to set self.canDisplayBannerAds to false from my skscene, The code I used did nothing. Any help would be appreciated.
func removeAds() {
let viewController: GameViewController = GameViewController()
viewController.canDisplayBannerAds = false
}
The reason why your code isn't working is because you are creating a NEW GameViewController and setting canDisplayBannerAds on that.
You need to keep a reference to your original GameViewController which you should be able to access from within your scene via your scenes SKView.
If you don't want to subclass your SKView you can use the following to get your current viewController.
if let viewController = view.nextResponder as? GameViewController /* or whatever your VC is */ {
viewController.canDisplayBannerAds = false
}
If your SKView is a subview change view.nextResponder to view.superview.nextResponder.
Alternatively you can use Notification Center to send a message to your GameViewController
In your GameViewController.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "turnOffAds", name: "TurnOffAdsNotification", object: nil)
}
func turnOffAds() {
self.canDisplayBannerAds = false
}
Somewhere in your SKScene:
NSNotificationCenter.defaultCenter().postNotificationName("TurnOffAdsNotification", object: nil)
Related
I know this question has been asked many times, however most aren't properly answered and do not follow the same set up as mine.
I have two UIViewControllers, GameViewController and MenuViewController. The MenuViewController uses a storyboard and UIKit. The GameViewController loads an SKScene. When the app starts it opens the MenuViewController. I have already made a button that segues to the Game on the Menu in the storybaord. That works, but now I am trying to do the opposite and return back to the Menu while in the Game. I have tried to use code that has answered similar questions but none seems to work.
Here is my GameViewController that I messed around with:
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView?{
if let scene = SKScene(fileNamed: "GameScene"){
let gameScene = scene as! GameScene
gameScene.gameViewControllerDelegate = self
gameScene.scaleMode = .aspectFill
view.presentScene(gameScene)
}
view.showsFPS = true
view.showsNodeCount = true
view.ignoresSiblingOrder = true
// Do any additional setup aft)er loading the view.
}
}
func callMethod(inputProperty:String){
print("inputProperty is: ",inputProperty)
}
And the code I added to my GameScene:
class GameScene: SKScene, SKPhysicsContactDelegate {
weak var gameViewControllerDelegate:GameViewControllerDelegate?
override func didMove(to view: SKView) {
gameViewControllerDelegate?.callMethod(inputProperty: "call game view controller method")
I'm completely stuck at trying to perform a segue out of the 5th and final scene of my SpriteKit game to another View Controller in the project(not the GameViewController, nor the root view controller).
I tried running self.view!.window!.rootViewController!.performSegueWithIdentifier("finalSegue", sender: self), from my finalScene, but it literally does nothing (the line gets triggered, it reads the right ViewController - i check by "print(self.view!.window!.rootViewController!)" console prints "segue read" as I instructed it, right after the segue command, as a check, the segue identifier is correct, but nothing happens).
Have tried calling a method that performs the segue from the GameViewController ( the view controller from which I am launching the view of the 5 SKScenes), I get "unexpectedly found nil while unwrapping an optional value". Tried performing the segue from the final scene ("finalScene.swift"), same error.
Have tried everything and all relevant solutions in other questions in the forum, as well as all combinations of nil/self/viewController in the "sender:" field of the performSegue method, to no avail. Here is the code that I am trying to make work which gives "unexpectedly found nil while unwrapping an optional value", pointing at the viewController var, but giving uncomprehensible debugging when loaded both on the device and on the simulator. It seems "nil" passes into the viewController var I am declaring, instead of the original GameViewController?
All my segue identifiers are correct in Storyboard, everything checked multiple times...What am I doing wrong? Should I do something different, given its the 5th SKScene and not the 1st (as in other solutions)? The segue into the SKScenes by segueing into the GameViewController from another UIViewController works fine - its the exit out of them that does not work. Many thanks for any help, completely stuck here!
Here is my relevant code:
In my GameViewController (UIViewController that launches my 5 consecutive SKScenes):
class GameViewController: UIViewController {
let myScene = finalScene()
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
//this is the 1st out of 5 SKScenes in the project
let scene = GameScene(size: CGSize(width: 2048, height: 2742))
//this is the 5th out of 5 scenes, that I am trying to trigger the segue out of
myScene.viewController = self
view.presentScene(scene)
view.ignoresSiblingOrder = true
}
}
}
Im my 5th scene, finalScene:
class finalScene: SKScene {
var viewController: GameViewController!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
let positionOfTouch = touch.location(in: self)
let tappedNode = atPoint(positionOfTouch)
let nameOfTappedNode = tappedNode.name
if nameOfTappedNode == "continue" {
self.viewController.performSegue(withIdentifier: "finalSegue", sender: self)
}
}
}
Just for anyone who might come across this, I solved it by using notification center. Ie added a notification observer on the parent view controller of the game scenes (which calls a function that performs the segue), and at the end point in the game where I wanted to do the segue, I posted to that notification, and it works great.
adding observer in ViewDidLoad of UIViewController:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.doaSegue), name: NSNotification.Name(rawValue: "doaSegue"), object: nil)
}
func doaSegue(){
performSegue(withIdentifier: "toNext", sender: self)
self.view.removeFromSuperview()
self.view = nil
}
And then calling it from within the game SKScene:
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "doaSegue"), object: nil)
You are calling the segue on the root viewController. I think that is the problem. You need to call the segue on the scene's viewController instead (where I am assuming you have created the segue, hence it is not being found on the root viewController).
Now the problem is that an SKScene does not have direct access to it's viewController, but just the view in which it is contained. You need to create a pointer to it manually. This can be done by creating a property for the SKScene:
class GameScene: SKScene {
var viewController: UIViewController?
...
}
Then, in the viewController class, just before skView.presentScene(scene)
scene.viewController = self
Now, you can access the viewController directly. Simply call the segue on this viewController:
func returnToMainMenu(){
self.viewController.performSegueWithIdentifier("menu", sender: vc)
}
Found this method to work just as well. But I do prefer the method mentioned above since it's fewer lines. I do wonder which is better for performance...
QUESTION: How can I dismiss a ViewController from my GameScene.swift ?
SITUTATION: I have 2 VCs in my SpriteKit Game, like so:
ViewController.swift ----Press Play-----> GameViewController
When the player loses, I want to dismiss the GameViewController so the player can press play again. I check for the player's loss in my GameScene.swift and would like to dismiss the GameVC from there.
N.B.: Googled this without success.
WHAT I TRIED:
1) Creating a gameVC instance in my GameScene.swift and dismissing it like so:
let gameVC = GameViewController()
gameVC.dismissViewController(false,completion: nil)
2) Doing:
self.view.window!.rootViewController?.dismissViewControllerAnimated(false, completion: nil)
Those don't work for obvious reasons ^^
You don't want to "grab" the existing instance: https://pragprog.com/articles/tell-dont-ask
You need to either hand GameScene a reference to the view controller so it can dismiss it, or use the delegate pattern to communicate backwards to a controlling object that the VC should be dismissed/dismiss itself.
A simple example… you can add a GameViewController property to GameScene, then dismiss the VC at the appropriate time:
class GameScene: SKScene {
var gameVC: GameViewController?
func gameDidEnd() {
gameVC?.dismissViewControllerAnimated(true) {
// if desired, do any cleanup after the VC is dismissed
}
}
}
Then, just set this property when creating the GameScene object in the first place:
if let gameScene = GameScene(fileNamed: "MyScene") {
gameScene.gameVC = someGameVC
}
This simple approach will tightly couple GameScene and GameViewController, making it a bit more difficult if you ever want to use one of these objects without the other. But for this simple use case, it may be fine.
I've follow some of your discussion. I want to add some code, because usually I prefeer to work with one ViewController or two and many SKScene and SKNode, but in this case could be useful to have a currentViewController reference:
class MyModelScene: SKScene {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var currentViewController : MyModelViewController! = MyModelViewController()
// MyModelViewController is a customized UIViewController
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
print("---")
print("∙ \(NSStringFromClass(self.dynamicType))")
print("---")
}
}
class Level1Scene: MyModelScene {
...
}
In the UIViewController:
class PreloadViewController: MyModelViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = Level1Scene(fileNamed:"Level1Scene") {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsPhysics = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .ResizeFill
scene.currentViewController = self
skView.presentScene(scene)
}
}
}
With this code , you've always a currentViewController reference in your SKScene and you can check if it's the correct viewController you want to dismiss or not.
When you press a button a custom segue is performed to start a game:
self.performSegueWithIdentifier("segueToGame", sender: nil)
I'm using sprite kit. I have settings in the GameViewController and GameScene such that you play until you lose and when you do the gameScene sends a message to the view controller telling it to segue back to the initial view controller. I do this as follows:
let notificationCenter = NSNotificationCenter.defaultCenter()
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
/*
All code to set the scene is there
*/
skView.presentScene(scene)
notificationCenter.addObserver(self, selector: "segueToHomeScreen", name: "segueToHomeScreen", object: nil)}
override func viewDidDisappear(animated: Bool) {
if self.isBeingDismissed() {
println("Dismissed Vc")
}else {
println("still here")
}
}
func segueToHomeScreen(){
self.performSegueWithIdentifier("segueToHomeScreen", sender: nil)
}
in my GameScene the code I used when you lose the game is:
func segueToHomeScreen() {
notificationCenter.postNotificationName("segueToHomeScreen", object: nil)
}
The problem is the GameScene has not completely stopped when I segue back to the home screen. I found this out by two things. One, the viewDidDisapper function you see above, I get a log message of "still here". Two, in gameScene I used this
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
println("Scene On")
}
When you lose the game and you are back at the home screen this function is still running. How can I stop the ViewController and GameScene from running after performing the segue to the home screen?
Mot sure if this is related but I also get an error message:
Warning: Attempt to present * on * whose view is not in the window hierarchy!
I'm am using a NSNotificationCenter in an attempt to control a timer in a SpriteKit. The code runs fine when I first enter the SKScene but when I try and re-enter the SKScene I'm getting an EXC_BAD_ACCESS error. I think that this is related to the removeObserver function. I'm not sure when to remove the observer, I tried to do it in the prepareForSegue function with no success. My viewController is as follows:
class JobStartedViewController: UIViewController {
var titleOfJob: String = ""
override func viewDidLoad() {
super.viewDidLoad()
let skView = self.view as! SKView
let scene:SKScene = GameScene.init(size: skView.bounds.size)
NSNotificationCenter.defaultCenter().postNotificationName("stopTimerNotification", object: nil)
NSNotificationCenter.defaultCenter().postNotificationName("startTimerNotification", object: nil)
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
and I add my observers to my GameScene.swift as follows:
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "stopTimerInBackground:", name:"stopTimerNotification", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "startTimerInBackground:", name:"startTimerNotification", object: nil)
Here's the likely flow of events:
You present JobStartedViewController, it creates the scene and adds it to the view, triggering didMoveToView(_:) and addition of two observers.
You dismiss the view controller or remove the scene from the SKView. At some point shortly after, there are no more strong references to the scene and it gets deallocated. At this point there are still unsafe references to it in the notification center.
You present another JobStartedViewController or otherwise post the stopTimerNotification notification.
NSNotificationCenter tries performing the selector on the deallocated scene and crashes your app.
The normal practice when using NSNotificationCenter is to remove your observer in the dealloc method for Objective-C or the deinit method for Swift:
class GameScene: SKScene {
// ...
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
If you are planning to add and remove that scene from your view multiple times you should also consider removing your observers in willMoveFromView(_:).