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!
Related
Normally I can find out when a View Controller appears with
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
This won't be called though if the user presses the home button or for some other reason the app goes to the background and then returns to the foreground. To find out when the app comes to the foreground I can add an observer to the Notification Center.
class FirstViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterForeground), name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("FirstViewController")
}
#objc func appWillEnterForeground() {
print("app in foreground")
}
}
However, my problem is that I have a tabbed app I want to know which View Controller is active when the app comes back into the foreground. The Notification center just sends a general message. Even though I an setting the notification observer in the first tab, it could be on any tab when the app goes into the background.
NSNotification.Name.UIApplicationWillEnterForeground
is a notification thrown by Notification Center. So obviously that is not related to any specific VC. What you can do rather is,
#objc func appWillEnterForeground() {
if self.viewIfLoaded?.window != nil {
// viewController is visible
}
}
Though notification of App entering foreground gets triggered to every viewController observing it, only the VC which is currently loaded and visible will have its code in if condition executed. That gives you a control to decide which VC is currently visible.
EDIT 1:
All that you want to figure out is the top ViewController in navigation stack of TabBarControllerwhen app comes to foreGround, you can add the observer for NSNotification.Name.UIApplicationWillEnterForeground only in UITabBarControllerand in
#objc func appWillEnterForeground() {
var vc : UIViewController = tabBarController.viewControllers![tabBarController.selectedIndex]
while vc.presentedViewController != nil || self.childViewControllers.count != 0 {
if vc.presentedViewController != nil {
vc = vc.presentedViewController!
}
else {
vc = vc.childViewControllers.last!
}
}
print("\(vc) should be the top most vc")
}
Use the Notification Observer and your appWillEnterForeground() or any event which gets fired from the observer in all view controllers under tab controller. So whichever the view controller you came back to will get your notification event get triggered in that particular VC. If you're looking for a centralized solution, this scattered gun approach may not work.
I'm making a small game in SpriteKit where I have a bool value stored that checks if it's the first time the user plays my game. If it is, I want the app to redirect him to another viewcontroller where he enters his character-name.
However, I'm having trouble getting my app to do so.
In my "GameViewController.swift" I have this code:
override func viewDidLoad() {
super.viewDidLoad()
checkGame()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// 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
}
}
func checkGame() {
//Check game
let firstPlay:Bool = appData.bool(forKey: "\(GameData.firstPlay)")
print(firstPlay)
if firstPlay == false {
print("Heading for setup")
self.performSegue(withIdentifier: "GoToSetup", sender: nil)
}
}
However, the "setup viewcontroller" isn't loaded. I can manage to tricker the segue with a button-click, but that's not what I want. I want it to happen as soon as the view is loaded.
Any ideas?
I don't know what you meant by "setup viewcontroller" isn't loaded", but usually we don't perform segues in the viewDidLoad. At this time, the view has just been loaded into memory and has not appeared yet. Performing a segue at this time is kind of a weird thing to do.
I recommend you to perform the segue in viewDidAppear():
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated: animated)
performSegue(withIdentifier: "GoToSetup", sender: nil)
}
You can programmatically call a segue with the following:
self.performSegueWithIdentifier("segueId", sender: self);
As it was said, in viewDidLoad stage UIViewController can't be done any UI-interaction, and performSegue to, because view is not at the view hierarchy yet. You can do this at viewDidAppear(animated:Bool).
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...
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)
How can I handle global events triggered by the notification centre for example in my API class I fire an event if an error response is received e.g. (500). When that event is fired an UIAlert should be displayed on what ever view controller is active, or on logout the login view controller should be presented.
As far as I can see there is no easy way to get the current view controller in order to interact with it. (Note that my root view controller is NOT a navigation controller).
An alternative solution, that will work regardless of whether your view controllers are embedded in a UINavigationController or not, would be to subclass UIViewController. This class will handle receiving the NSNotification that an error occurred and will also handle displaying the alert:
class MyViewController: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "errorOccured",
name: "ErrorNotification",
object: nil)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self, name: "ErrorNotification", object: nil)
}
func errorOccured() {
// Present an UIAlertViewController or the login screen.
}
}
Now, any UIViewControllers that should display an alert when the error notification is posted just have to be a subclass of MyViewController. Just make sure, if you override viewWillAppear or viewWillDisappear, that you call super.viewWillAppear or super.viewWillDisappear.
Is this way too hard to get current view controller ( when not using navigation controller ) ?
// on your app delegate
getCurrentViewController(self.window!.rootViewController!)
func getCurrentViewController(viewController:UIViewController)-> UIViewController{
if let navigationController = viewController as? UINavigationController{
return getCurrentViewController(navigationController.visibleViewController)
}
if let viewController = viewController?.presentedViewController {
return getCurrentViewController(viewController)
}else{
return viewController
}
}
For BroadCast Notification
NSNotificationCenter.defaultCenter().postNotificationName("erro400", object: nil)
For Receive
override func viewWillAppear(animated: Bool) {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "ErroOccure", name: "erro400", object: nil)
}
func ErroOccure()
{
//present alert from here
// do whatever you want
}
You have to Remove Notification when you finish with it.
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}