Segue in SKScene to UIViewController - ios

In my GameScene.swift file, I am trying to perform a segue back to my Menu View Controller like so:
func returnToMainMenu(){
var vc: UIViewController = UIViewController()
vc = self.view!.window!.rootViewController!
vc.performSegueWithIdentifier("menu", sender: vc)
}
This method is run when a node is tapped:
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if gameOn == false{
if restartBack.containsPoint(location){
self.restartGame()
}
else if menuBack.containsPoint(location){
self.returnToMainMenu()
}
else if justBegin == true{
self.restartGame()
}
}
}
}
Where menuBack is the button back to the menu. Every time I run this code, I am thrown an NSException:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver (<ProxyBlock.Menu: 0x165a3e90>) has no segue with identifier 'menu''
I checked my segue's identifier and it was indeed "menu".

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 {
weak 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(){
viewController?.performSegueWithIdentifier("menu", sender: vc)
}

How to segue from Scene to ViewController
Swift 3 - Works with SpriteKit / UIKit
You can use NSNotification.
Example:
1.) Create a segue in the storyboard and name the identifier "segue"
2.) Create a function in the ViewController you are segueing from.
func goToDifferentView() {
self.performSegue(withIdentifier: "segue", sender: self)
}
3.) In the ViewDidLoad() of your ViewController you are segueing from create the observer.
NotificationCenter.default.addObserver(self, selector: #selector(goToDifferentView), name: "segue" as NSNotification.Name, object: nil)
4.) In the ViewController or Scene you are segueing to, add the Post Method wherever you want the segue to be triggered.
NotificationCenter.default.post(name: "segue" as NSNotification.Name, object: nil)

Related

Swift - access UIButton inside ContainerView in another ViewController

I am having a problem accessing my UIButton that's inside a ContainerView from another ViewController.
If the user taps the button a SubView should appear inside of my ViewController. I tried dragging the button in my ViewController file to create an #IBAaction func tapped() but that is not working.
It only works inside of the ContainerView.swift file. But I can not create my Subview there...
I hope you get my problem, I am grateful for every help!
import UIKit
class ContainerViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func addButtonTapped(_ sender: Any) {
print("add Button")
}
You can reach your ViewController inside your ContainerView using its parent property. So, inside your ContainerView class:
if let parentVC = self.parent as? ViewController {
parentVC.showSubView() // your method to show the sub view.
}
You can achieve this using NotificationCenter and Delegates. Here is how you can achieve it with NotificationCenter
In your ContainerViewController you can post notification on button click
#IBAction func yourBtnTap(sender : UIButton) {
NotificationCenter.default.post(name: Notification.Name("myBtnTapped"), object: nil)
}
In your CurrentViewController you have to first register to receive this notification. So in your viewDidLoad, do this:
NotificationCenter.default.addObserver(self, selector: #selector(self.myBtnTappedAction(notification:)), name: Notification.Name("myBtnTapped"), object: nil)
Now in your CurrentViewContoller create myBtnTappedAction function that will be called whenever the button is tapped.
#objc func myBtnTappedAction(notification : Notification) {
// now you can perform all your actions here. this function will be called everytime the button in the container view is tapped.
}

UIViewController will not segue to another UIViewController?

I have my GameViewController that contains a SCNView. I can segue to my GameViewController but when my player dies I try to segue to my GameOverVC but it does not segue. I know the code is being called because I print something to the console. The segue is setup in the storyboard.
I segue by calling
self.performSegue(withIdentifier: "gameOver", sender: self)
I have done this in multiple apps but I can not figure out why this is not working. This is my first app with SceneKit so I don't know if that changes anything but any help is appreciated!
Also
If I try
let vc = self.view.window?.rootViewController
vc?.present(GameOverVC(), animated: true, completion: nil)
Then it does present the UIViewController but it does not show anything I have added in that view?
Somehow I had the same problem and asynchronous-call rescue me:
I did something like this:
DispatchQueue.main.asyncAfter(deadline: .now()) {
self.performSegue(withIdentifier: "gameOver", sender: self)
}
if you need to segue from an object that is not a ViewController inherited class, you need to pass a ref of the parentViewController to your object and make the call like this:
DispatchQueue.main.asyncAfter(deadline: .now()) {
parentViewController.performSegue(withIdentifier: "gameOver", sender: self)
}
segue works for UIVIewControllers not for SpriteKit scenes. So, you need to perform segue from parent viewcontroller, so you can do something like,
self.parentViewController.performSegue(withIdentifier: "gameOver", sender: self)
Or as mentioned by #ZeMoon in this answer, you can make property of your viewcontroller in SKScene class and you can perform segue over this property something like ,
class GameScene: SKScene {
var viewController: UIViewController?
...
}
Then, in the viewController class, just before skView.presentScene(scene) do,
scene.viewController = self
Now you can perform segue like,
self.viewController.performSegueWithIdentifier("gameOver", sender: self.viewController)
OR
Forget segue just instantiate your GameOverVC and push it on your navigation controller like,
let nav = self.view.window?.rootViewController as! UINavigationController
nav.pushViewController(gameOverVC, animated: true)
Or
self.navigationController?.pushViewController(gameOverVC, animated: true)
You can try following :-
UIViewController *vc = self.view.window.rootViewController;
[vc performSegueWithIdentifier:#"id" sender:nil];

Unable to segue from Spritekit SKScene to another UIViewController, XCODE8/Swift

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...

Initial func in 1st view from 2nd view (swift)

I have a button on 2nd viewController, after pressing that button, I would like to dismiss the 2nd viewController and go back to the 1st view controller and immediately call a function that coded inside 1st ViewController swift file.
May I know how can I do that? By segue?
There are many way to do this one of the best way is using protocol and delegate.
You can create one protocol and extend that protocol in your ViewController1. Now create the delegate of protocol in ViewController2 and pass reference of that delegate in the ViewController1's prepareForSegue method.
First create one protocol like this
protocol PassdataDelegate {
func passData()
}
Now extend this protocol in ViewController1 like this and pass the reference of delegate in prepareForSegue method
class ViewController1 : UIViewController, PassdataDelegate {
func passData() {
//Here call your function
self.callMyFunction()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "SegueIdentifier") {
let destVC = segue.destinationViewController as! ViewController2
destVC.delegate = self
}
}
}
Now create the delegate object of protocolin ViewController2 like this
class ViewController2 : UIViewController {
var delegate: PassdataDelegate?
//Now call the method of this delegate in Button action
#IBAction func buttonClick(sender: UIButton) {
self.delegate.passData()
//Now dismiss the controller
}
}
Note: - Here i am passing stringbut you can pass any type of object that you have declare in your delegate method.
You can refer unwind segue.
class ViewController1 {
#IBAction func doSomeStuffAfterReload(segue: UIStoryboardSegue) {
// do whatever you need to do here.
}
}
On storyboard, from ViewController2 Ctrl+Drag from the button to the exit outlet and select doSomeStuffAfterReload.
You can see it in action here: https://spin.atomicobject.com/2014/10/25/ios-unwind-segues/
Happy coding^^

After programmatically segueing, The ViewController has not been dismissed

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!

Resources