Hello I have code set up that should correctly segue from GameScene to another view controller, mainMenuViewController. The code I have set up to achieve this is
var viewController: GameViewController!
func segue() {
viewController.dismissViewControllerAnimated(true, completion: nil)
SettingsViewController.delete(self)
}
In GameViewController
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let skView = view as! SKView
let scene = GameScene(size: skView.bounds.size)
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = false
//skView.showsPhysics = true
scene.scaleMode = .AspectFill
skView.presentScene(scene)
scene.viewController = self
}
}
which is called when a SKLabelNode is tapped. I received advice to use this since the mainMenu is the scene that is first loaded when the player starts the game and they segue to gameScene so I was told to dismiss GameScene instead of creating a new segue every time. I get the error 'fatal error: unexpectedly found nil while unwrapping an Optional value' and a crash whenever the button is tapped. I assume it is talking about the viewController. Thank you for your help!
If understand correctly your problem, you are trying to dismiss GameViewController. But method dismissViewControllerAnimated only dismisses controller that you are currently presenting. So as your game view controller is not presenting anything it can't dismiss it. As you say from code to GameViewController dismiss controller that was presented by him, it tries to do as you want so he gets his property of presented view controller and try to dismiss it, but that property is nil so such error gets. To fix this problem you should call viewController.presentingViewController.dismissViewControllerAnimated(true, completion: nil) .
Property presentingViewController returns controller that presents you, in your case controller that present GameViewController. As you get this controller you can dismiss controller that was presented by him(in you case presented controller is your GameViewController). So logic is to get as to say "parent" controller that has permissions to dismiss yourself(GameViewController)
I need to look to your code. It seams now that you try to call method delete to view controller, but as I know view controller doesn't have such. So think that is other bug in your program, and mainly you are setting Noble_Ninja.SettingsViewController to other object that expect to have such method. That is my prediction I can't say anything clearly because i only has exception description. Try to find where you calling this method in your code , than put brakepoints there and when program stops on your brakepoint write in your console(log) - (po NAME_OF_INSTANCE_THAT_CALLS_SUCH_METHOD) -, it will show real instance type, so with this info you can detect where is real problem. Good luck
Related
I want to present a view controller:
Modally
Using custom transitions
At the same time, I want to make sure that when it is dismissed, the view controller behind it is aware of being pushed to the foreground.
My basic idea is to try some different configurations and see which one will lead to viewWillAppear being called on the view controller behind.
Attempt 1
presentedViewController.modalPresentationStyle = .Custom
presentedViewController.transitioningDelegate = someTransitioningDelegate
The results of this approach:
The custom transition works perfectly well
viewWillAppear does not get called on the view controller behind presentedViewController when I call presentedViewController.dismissViewControllerAnimated(true)
I do want viewWillAppear to be called on the view controller below the one being dismissed, so I did this:
Attempt 2
presentedViewController.modalPresentationStyle = .FullScreen
presentedViewController.transitioningDelegate = someTransitioningDelegate
or
presentedViewController.modalPresentationStyle = .FullScreen
presentedViewController.modalTransitionStyle = .CoverVertical
presentedViewController.transitioningDelegate = someTransitioningDelegate
The results of this approach:
viewWillAppear gets called on the view controller behind presentedViewController when dismissing presentedViewController
The transition occurs as expected when presenting the view controller.
When dismissing the view controller, the background during the transition is black, which is undesirable.
Seems that .FullScreen causes the view controllers behind presentedViewController to be removed from the display hierarchy - which is good because presumably that's what triggers the viewWillAppear call.
Attempt 3
presentedViewController.modalPresentationStyle = .FullScreen
presentedViewController.modalTransitionStyle = .CoverVertical
The results of this are:
viewWillAppear gets called on the view controller behind presentedViewController.
The background during the transition is the view controller located behind presentedViewController, which is desired.
No custom transition.
The project I'm working on is structured in a way that makes it difficult to use delegation (which seems to be the suggested answer here). Making use of NSNotificationCenter is another alternative which lets me call the code that is supposed to be called by viewWillAppear, but from attempt 3, I'm hoping there is a more elegant approach to achieve all these:
Trigger viewWillAppear
Use a custom transition
See the view controller being presented in the background during the transition animation
Seems Apple considers it foul play to invoke viewWillAppear etc., but it's okay to invoke beginAppearanceTransition and endAppearanceTransition, which in turn will invoke viewWillAppear. I'm going with this.
The way I have achieved this in the past is by calling viewWillAppear and viewDidAppear from the transition animator. Here's a simplified example:
public func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
fromVC.viewWillDisappear(true)
toVC.viewWillAppear(true)
UIView.animateWithDuration(0.3, animations: {
//Animations here
}, completion: { (success) in
toVC.viewDidAppear(success)
fromVC.viewDidDisappear(success)
transitionContext.completeTransition(true)
})
}
I call the "will" methods before I do animations, and the "did" methods after completion
This the code block I'm having trouble with.It gives me error because of 'presentViewController'
func authenticateLocalPlayer(){
let localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {(viewController, error) -> Void in
if (viewController != nil) {
self.presentViewController(viewController!, animated: true, completion: nil)
}
else {
print((GKLocalPlayer.localPlayer().authenticated))
}
}
}
I know it's because SKScene has already has a UIViewController, but I don't know how else to represent it.
There is simple way to deal with it within the scene. Try it and let us know. I don't disagree with answer above completely, but they should have guided you better and given the right answer. Yes You have to call presentViewController on a UIViewController. Since your SKScene is using SKView which is subclass of View but not UIView at all, so calling presentViewController from self means you are asking SKView object to present a UIViewController which SKView cannot. But here is a simple solution, as SKView is subclass of UIView itself, so ask SKView's root view controller to do this for you like this.
Your call will be like this (I am writing it in objective C as I do not code in swift but same is true for both languages)
[self.view.window.rootViewController presentViewController:viewController animated:YES completion:nil];
As you see instead of asking 'self', you have to ask 'self.view.window.rootViewController'. And you can do it right inside your scene. There you go.
You can't call presentViewController from within your SKScene, you need to call it on a UIViewController.
Therefore you want to find the parent view controller that contains your Sprite Kit content and call the function there.
What I want to achieve: segue from gameVC to mainmenuVC and get rid of the gameVC
When the app starts it first shows a main menu viewcontroller with a play button that segues to the gameviewcontroller. When the user taps on a menu button sprite the following function in the gameviewcontroller gets called and it segues back to the main menu:
func returnToMainMenu () {
//This works but does not deinit the vc
navController?.dismissViewControllerAnimated(true, completion: nil)
/* this does not do anything:
navController?.popViewControllerAnimated(true)
*/
}
This is probably not how it is done properly and I think that might be the problem, but I could not get to work otherwise because gameViewController.navigationViewController is nil.
This is how my storyboard looks:
This is how the memory usage looks when the app is running. Those spikes/steps occur whenever the gameviewcontroller is loaded. It seems to me that the problem is, that the gameviewcontroller does not de-initialize when returnToMainMenu() is called.
Also, this never gets executed:
deinit {
debugPrintln("GameViewController deinitialized")
}
update:
I deleted this
navController = self
and defined navController in returnToMainMenu like this:
let navController = view.window?.rootViewController as! UINavigationController
segue back to main menu still works but it still does not deinit the vc
Of course it will memory leak.
override func viewDidLoad() {
navController = self
}
You just gave yourself a reference to itself. Usually when your vc goes offscreen, the view hierarchy no longer holds the view so the view is deinited. You set a reference to itself so whatever you do, it will always hold itself in memory and will never deinit.
I figured it out after watching Lecture 8 of the stanford iOS8 course (at 14:23).
The problem was that I added a reference to the gameviewcontroller in my gamescene to call its returnToMainMenu() method from the scene. In order for its memory to be cleared all the references to the VC have to be set to nil.
I solved it by referring to the navigation controller directly from my scene like this:
(scene!.view!.window?.rootViewController as! UINavigationController).dismissViewControllerAnimated(false, completion: nil)
I'm presenting a modal view controller using a custom transition (by setting its modelPresentationStyle to UIModalPresentationCustom, providing a transitioning delegate, and UIViewControllerAnimatedTransitioning object).
In the presented view controller, I have an unwind segue hooked up to a button. The segue fires just fine; the IBAction method in the presenting view controller is called, and so is prepareForSegue in the presented view controller. However, the presented view controller is not dismissed, and the appropriate transitioning delegate method (animationControllerForDismissedController:) is not called.
If, however, I set the presented view controller's modalPresentationStyle to UIModalPresentationFullScreen (the default), the view controller is dismissed properly (this breaks my custom transition, though).
I'm at a complete loss at what to do here. I've looked through Apple's documentation, and didn't notice anything saying that one had to do special things with unwind segues when dealing with custom transitions.
I'm aware that I could call dismissViewControllerAnimated:completion: in the IBAction method of the presenting view controller, but I'd rather use that as a last resort, and get the unwind segue working the way it should (or at least know why it's not working :) ).
Any help would be much appreciated,
Thanks in advance
It seems that if you use UIModalPresentationCustom to present the controller with a custom transition manager, you also need to use a custom transition manager to dismiss it (which makes sense I guess, since you can do all kinds of weird stuff in the animator object and UIKit can't be sure that just dismissing the screen as usual will completely restore the original state - I just wish it told you that explicitly...).
Here's what I've done to fix this in my app:
override segueForUnwindingToViewController in the parent view controller (the one to which you're moving after the dismiss animation) and return an instance of your UIStoryboardSegue, either the one you've used for the original transition or a new separate class
if the unwind segue's target view controller is in a navigation hierarchy, then you need to override that method in the navigation controller instead
in the perform method call dismissViewControllerAnimated
the presented view controller needs to still hold a valid reference to the transitioning delegate, or you'll get an EXC_BAD_ACCESS (see DismissViewControllerAnimated EXC_Bad_ACCESS on true) - so either make it keep the delegate as a strong reference as described in that thread, or assign a new one before calling dismissViewControllerAnimated (it's possible that changing modelPresentationStyle to e.g. full screen before dismissing would work too, but I haven't tried that)
if the dismiss animation needs to do any non-standard things (mine luckily didn't), override animationControllerForDismissedController in the transition manager object and return a proper animator
if the target view controller is in a navigation hierarchy, then you also need to manually pop the navigation stack to the target controller before dismissing the presented screen (i.e. target.navigationController!.popToViewController(target, animated: false))
Complete code sample:
// custom navigation controller
override func segueForUnwindingToViewController(toViewController: UIViewController,
fromViewController: UIViewController,
identifier: String?) -> UIStoryboardSegue {
return CustomSegue(
identifier: identifier,
source: fromViewController,
destination: toViewController
)
}
// presented VC
var customTransitionManager: UIViewControllerTransitioningDelegate?
// custom segue
override func perform() {
let source = sourceViewController as! UIViewController
if let target = destinationViewController as? PresentedViewController {
let transitionManager = TransitionManager()
target.modalPresentationStyle = .Custom
target.customTransitionManager = transitionManager
target.transitioningDelegate = transitionManager
source.presentViewController(target, animated: true, completion: nil)
} else if let target = destinationViewController as? WelcomeViewController {
target.navigationController!.popToViewController(target, animated: false)
target.dismissViewControllerAnimated(true, completion: nil)
} else {
NSLog("Error: segue executed with unexpected view controllers")
}
}
I also met this problem when I need to pass data back from the modalpresented view.
I wandering around Google and here for a couple of hours but I couldn't find an answer that is easy to understand for me. But I did get some hint and here's a work around.
It seems that because it has to pass data back, and the dismissing process from the automatic Unwind is prior before the data passing which prevented the ViewController being dismissed. So I think that I have to manually dismiss it once one more time.
I got some luck here. I didn't notice that it was a child viewcontroller. I just configured it from the storyboard.
And then in the Unwind function, I added to lines to remove the child viewcontroller and the child view. I have no code in the sourceViewController.
Swift 4.1
#IBAction func unwindToVC(sender :UIStoryboardSegue){
if let source = sender.source as? CoreLocationVC{
if source.pinnedCity != nil{
clCity = source.pinnedCity
}
if source.pinnedCountry != nil {
clCountry = source.pinnedCountry
}
if source.pinnedTimeZone != nil {
clTimeZone = source.pinnedTimeZone
}
if source.pinnedLocation != nil {
clLocation = source.pinnedLocation
}
// I added 2 lines here and it just worked
source.view.removeFromSuperview()
source.removeFromParentViewController()
}
When Im finished with my SKScene is there a way to dismiss the SKScene from within my SKScene class?
If not back in my Viewcontroller where I present my SKScene [skView presentScene:theScene]; is there a way to restart the scene or remove in from my SKView?
The SKScene Class Reference and SKView Class Reference are no help.
Update:
The following code removes my scene from my SKView [yourSKView presentScene:nil]; but when Im back in my view controller the scene is still running in the background. I can always pause it when the game is over and I'm sent back to my view controller(menu) but I'm wondering is there another method other then pausing it like completely removing it?
-(void)endTheGame {
[highscoreLabel removeFromSuperview];
NSLog(#"Game Over");
//would like to end here before calling the below method in my view controller
[self.delegate mySceneDidFinish:self];
}
Having met a similar issue I stumbled around your question, and since nobody gave a decent answer here's how I solved it:
in my scene I called both lines
[self removeFromParent];
[self.view presentScene:nil];
in my controller (the controller that displays the SKScene) I changed the template code from Apple, which was creating and presenting my scene from viewDidLoad in order to create and present my scene in viewWillAppear, only if the view's scene is nil
here's my Swift code, even if you're using Objective C you'll understand what it does; the key line being the "if skView.scene == nil" test :
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let skView = self.view as SKView
if skView.scene == nil {
let scene = GameScene(size:skView.bounds.size)
scene.controller = self
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
You can use:
[yourSKView presentScene:nil];
to remove the scene.
"You can't go "Back to the View Controller" from a scene. The scene is a View, the view controller controls and displays views. Use the view controller to change views. Remember the view controller itself is not a view." -Wharbio
Best solution here is to create another View Controller. This view controller will be my menu. Then the other viewcontroller will act as a host for the skscene.
In my situation I then use my menu viewcontroller to dismiss the viewcontroller displaying in the skview.
From within your SKScene, you can simply do [self.view presentScene:aNewScene] to present another scene
I completely remove scenes by using this block in my view controller:
Obviously you will need to declare your Size, and "newSKview"
SKView * skView = (SKView *)self.view;
SKScene *newScene = [[newSKView alloc]initWithSize:size];
newScene.scaleMode = SKSceneScaleModeAspectFill;
SKScene *oldScene=(skView.scene);
[oldScene removeFromParent];
[skView presentScene:newScene];
This works fine for me. None of my Scenes are retained or strong.
I prefer to use an additional UIViewController for SKView and Notification Center in where the UIViewController made SkScene.
The callback function is
#objc func cb_dismissingVC(_ notificaiton: Notification) {
self.dismiss(animated: true, completion: nil)
}
Maybe my variant will be helpful:
[[self.scene childNodeWithName:#"YourChildNodeName"] removeFromParent];