5

I wanted to see what the best practice(s) of accessing UIViewController methods from GameScene is/are. Right now I have been using NSNotificationCenter, but I don't want to use this due to specific functionality that I am trying to achieve.

Also, if there are not any other ways of gaining access to UIViewController through GameScene, then really what I am wondering is a method of being able to present an UIAlertController within the GameScene without UIViewController.

I would just create a global variable to the UIViewController, but I heard this is bad practice.

Thanks!

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Electric
  • 115
  • 10

3 Answers3

3

You can show UIAlertControllers in SKScenes, simply show them on the rootViewController, which is probably the best place to show them anyway.

self.view?.window?.rootViewController?.present...

I dont like referencing the GameViewController in SKScenes and I never actually got to a point where I was forced to do so. NSNotificationCenter, delegation or protocol extensions are the better way.

I actually use a helper for Alerts I made using Swift 2's protocol extensions because I like clean, reusable and as little duplicate code as possible.

Just make a new .swift file and add this code

import SpriteKit

protocol Alerts { }
extension Alerts where Self: SKScene {

func showAlert(title title: String, message: String) {

    let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)

    let okAction = UIAlertAction(title: "OK", style: .Cancel) { _ in }
    alertController.addAction(okAction)

    self.view?.window?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)
}

func showAlertWithSettings(title title: String, message: String) {

    let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)

    let okAction = UIAlertAction(title: "OK", style: .Cancel) { _ in }
    alertController.addAction(okAction)

    let settingsAction = UIAlertAction(title: "Settings", style: .Default) { _ in

        if let url = NSURL(string: UIApplicationOpenSettingsURLString) {
            UIApplication.sharedApplication().openURL(url)
        }
    }
    alertController.addAction(settingsAction)

    self.view?.window?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)
    }
}

Now in your scenes you need to show alerts you simply conform to the protocol

class GameScene: SKScene, Alerts {

} 

and call the methods like

showAlert(title: "Alert title", message: "Alert message")

as if they are part of the scene itself.

Enjoy

crashoverride777
  • 10,581
  • 2
  • 32
  • 56
  • This is excellent, can't wait to try out this extension. – Electric Mar 09 '16 at 17:59
  • My pleasure. I like the flexibility of it, say you now make an app thats not a game, you would simply change the extension to this "..... where Self: UIViewController. {" and now you can show the alerts on all your view Controllers. Maybe one day we can even add 2 things to this, which I think is not possible yet, like "... where Self: UIViewController, SKScene {" – crashoverride777 Mar 09 '16 at 18:02
  • Yes, the flexibility is great and convenient. This also helped me understand the benefits of using protocols/extensions. Gave me a couple ideas of how I can simplify my code in other parts of my project. Thanks for the great response! – Electric Mar 09 '16 at 18:06
  • Yeah protocol extension really are awesome. Been doing the same thing in my project the last few weeks. If you want to see another example, I made a social sharing helper. Before it used to be a struct with static func, now its just a protocol extension. http://stackoverflow.com/questions/34832258/how-can-i-create-a-share-to-facebook-button-in-a-spritekit-game-using-swift/34857172#34857172 – crashoverride777 Mar 09 '16 at 18:09
  • I upgrade my helper slightly incase you are interested. The syntax was a bit old and it turns out you also dont have to dismiss the alert controller manually. – crashoverride777 Mar 18 '16 at 16:34
  • it doesn't work for swift5 .. I don't see anything pops :( – Energy Jun 19 '21 at 09:16
0

This is a bit hacky, but to directly access functions from the view controller use this:

(view?.nextResponder() as? MyViewController)?.presentAlert()

This relies upon your SKView being the main view in your view controller. If it isn't then you'll need to add in .superview? after your view? call.

Doug
  • 2,972
  • 3
  • 22
  • 28
0

An alternative to the previous answer I posted (and the more proper method) would be to create a property in your scene class to store a reference to the view controller and use that:

class MyGameScene: SKScene {

    weak var viewController: MyViewController?

    func somethingHappened() {
        viewController?.presentAlert()
    }

}

And when you present the scene in your controller make sure to set a reference to your view controller:

if let scene = GameScene(fileNamed:"GameScene") {
    scene.viewController = self

    // other stuff
    skView.presentScene(scene)
}
Doug
  • 2,972
  • 3
  • 22
  • 28
  • Thanks for the response! I will test these out and let you know which works out better! – Electric Mar 09 '16 at 04:35
  • Check my answer, there is better ways than to link to the GameViewController. You can even show alerts directly in SKScenes if you want. No need for references to the ViewController. – crashoverride777 Mar 09 '16 at 14:31