Using multiple ViewControllers and SKScene in Swift - ios

Below I have two and a half different ways to set up my game using view controllers and SKScene. The half I haven’t put so much time into yet as I’m hoping one of these other solutions will work.
described in more detail below are:
MainViewController -> GameViewController(holds the SKScene)
GameViewController(holds the SKScene) - Using Protocols
0.5 Two SKScenes/Two swift custom classes.
Number 1
MainViewController -> GameViewController(holds the SKScene)
when the game is over in the SKScene - I want to dismiss GameViewController and return to MainViewController.
I’ve had no luck in getting that to work. I’ve tried dismissing the view controller and the closest I’ve come is dismissing the SKScene using something like(in SKScene:
self.view?presentScene(nil)
or
self.dismiss(animated: false, completion: nil)
self.presentingViewController?.dismiss(animated: false, completion: nil)
but I’m unable to go back to the MainViewController. I’ve tried creating a reference to the GameViewController to pop it like:
weak var gameViewController:GameViewController? = GameViewController()
Unfortunatly, I’m unable to find the right functions to dismiss it properly.
After a lot of searching without finding an answer I thought I would try a different method.
Number 2
GameViewController(holds the SKView) then have the GVC hold the buttons and labels that I wanted and show the scene on top. this was a fail as The buttons and labels don’t have a zPositions and appeared on top of the scene.
I tried doing .isHidden = true on them before calling the scene and that seemed to work but when I dismiss the scene and attempt to change them back to .isHidden = false (tried using protocols) I was unable to get it to work. (was going off of what stvn describes How to reference the current view controller from a sprite kit scene)
I suspect this second way could be a good way of doing it but maybe I’m just not approaching it correctly.
Number 0.5
then finally what I’m thinking about now is just making the second scene to “flip” back and forth between. I believe this is the recommended way of doing it but I’m not really sure how to approach it. If I did this version. I would like to have a gameScene.swift/gameScene.sks for each view (perhaps using something like presenting a uiviewcontroller for skscene.
I would prefer the first way with having two view controllers as I think that the layout for my game would be much easier to do using a few view controllers.
My least preferred method is multiple scenes as I’m just not that great with sprite kit and math for laying out everything.
Is the first method even possible? If so any suggestions would be great. If not what would be the recommended way of doing this?

In method 1 if your MainViewController is a UINavigationController you should have no issues pushing and popping whatever other ViewControllers you need to.
e.g. in GameViewController:
self.navigationController!.popViewController(animated: true)

Related

UINavigationController Interactive Pop Gesture only works first time

I have been working on an app for some time and just realized the swiping back in the detail view only returns me to the master view the first time. It also isn't smooth, even when it works on the first time. Instead of smoothly going to the master view, it jumps all at once, even when I swipe slowly. It used to work correctly, but I haven't been testing for this specifically, so I don't know when it stopped working and what I changed to cause this.
A little about how my app is setup...
I have a split view controller that is connected to my MasterTableViewController and DetailViewController.
Both of those are have TableViews and are embedded in Navigation Controllers.
I have set it up so that the app originally loads to the MasterTableViewController instead of going immediately to the DetailViewController, but even when I take this out, the interactive pop gesture doesn't work.
I don't believe I've messed with any of the back button controls. I have looked through my code and storyboard and can't find anywhere that I have. This is part of what is most confusing because these questions (1, 2, and 3) all seem to have problems stemming from changing the back button or can be fixed by entering the following line of code:
self.navigationController.interactivePopGestureRecognizer.delegate = nil
Adding that to my code seems to have no impact on how it behaves.
Here is a picture of how it is setup for reference:
I can usually figure out these things on my own, but this problem baffles me because it works the first time, but not any others. As far as I can tell, nothing changes between the first time and the others. I don't know if anybody else has had the same issue, but any help on why this might be happening would be greatly appreciated. I can provide code or answers to questions on how I am doing certain things if needed. I haven't put any in because there are so many different things controlling this piece that I don't know where to start.
When are you calling self.navigationController.interactivePopGestureRecognizer.delegate = nil?
Doing this will definitely disable interactive pop. It sounds like you may be calling this after a certain UIViewController appears.
What other modifications to UINavigationController are you making? Are you using appearance delegate?
Are you subclassing? If so, are you calling super in all of your method overrides?
Also check your overrides of viewWillAppear in child ViewControllers. This method gets called during an interactive pop. If you are doing a lot of computation (or synchronous calls) on the main thread within this method, it could cause frame drop, hence the choppy animation.
Hope this helps
From Alex Chase's answer : Also check your overrides of viewWillAppear in child ViewControllers. This method gets called during an interactive pop.
added it to viewWillAppear and it worked:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.interactivePopGestureRecognizer?.delegate = self
}

ARKit: pausing session does not stop the scene to be called

I'm using ARKit with SpriteKit. My AR feature is not a core feature in my app, users may optionally navigate to a viewController with an ARSKView where I configure and set an SKScene in the ARsession.
I pause the session when the user navigates back:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
then the viewController is pop from the navigationController where it was pushed. But I'm seeing that the func update(_ currentTime: TimeInterval) in my SKScene subclass keeps being called... which is the appropriate way to also stop that? I thought that pausing the ARSession would also stop the scene.
EDIT: when I navigate back from the view controller that holds the ARSKView and it is deinitialized, I see that the SKScene is still in memory and a new one is created every time I navigate again to the AR related view controller. So, if I navigate there N times, I see I have N SKScenes in memory. How could I appropiately remove the scene when navigating back? The scene property of the ARsession is read only.
that sounds like a memory retain issue. Make sure that any node in you SKScene subclass is properly removed and deinitialized.
Set your scene delegate to nil, set any node or array of nodes referenced in your VC to nil.
I ran a simple test with 2 VC embedded in a navigation controller, the first one has a view with a start button, the second one is the ARVC.
I run the session, set my custom scene delegate to ARVC, when I hit the back button everything stops by itself and my ARVC is being popped. Nothing in memory.
So make sure that you manually remove all your nodes and their animations, constraints, actions and anything that relates to you custom SKScene subclass.
Also don't be afraid to call sceneView.presentScene(nil) if that can help.
I couldn't find any official documentation about this, apparently there is not proper method to stop a session yet. What I found that was similar to your scenario is this work around.
It suggests you create ARSKView as disposable and before leaving your controller, pause the session, remove the view from superview and assign it to nil.

UINavigationController.set​View​Controllers is resulting in an empty viewControllers array

I am using the set​View​Controllers function on UINavigationController to set my first root view controller but the viewControllers array is empty after it has been called.
I know it is expected behavior to see the view controller in the viewControllers array immediately after setting, even if there is an animation so I'm not sure what could be causing the problem.
setViewControllers([viewControllerToPresent], animated: true)
Noteworthy: It is happening at app launch time but after I am certain the UINavigationController is loaded and ready.
The problem ended up being trying to simultaneously present a viewcontroller modally (tutorial slides) as the navigationcontroller was trying to push a viewcontroller on to the stack (both animated)
By delaying the presentation of the modals by 1 second, both view controllers were able to present without clobbering one another.
Would love to know the whys of this if anyone if familiar enough with UIKit and Animation APIs to explain. Will accept my own answer if I don't hear from anyone but will gladly change this if we get a better answer in the future.

Segue only working once?

Hi I made a game using SpriteKit and wanted to add a leaderboard using a UITableView. My segues are working perfectly from my GameViewController to the TableViewController and back. However I can only do this once. Once I am back to my GameViewController, tapping the same button wont take me to the TableViewController for the second time. Why is this happening?
I set it up so when a certain SKSpriteNode is tapped it segues to the the TableViewController. Currently I am using this line of code to segue from the GameViewController to the TableViewController (This code is within GameScene.swift)
self.view!.window!.rootViewController!.performSegueWithIdentifier("showScores", sender: self)
I then setup a Back button within a navigation bar and created a segue back to the GameViewController (by dragging the BarButtonItem to the GameViewController and selecting presentModally). Some help would be appreciated. Thanks!
I think you are segueing to the leaderboard, segueing back, but in the process adding the main game view to the stack rather than removing the leaderboard, so when you try to segue again, it is not the visible game scene that receives the call.
Try using this rather than a segue in your leaderboard code (where you ask to segue back to the game scene):
self.dismissViewControllerAnimated(true, completion: nil)
If that doesn't work use a delegate to ask the game scene to dismiss the leaderboard.
Edit: I was rambling and maxed out my word count for the comment, so thought I'd add my explanation here!
Think of the stack as a pile of paper, where each piece of paper is a viewController, the topmost piece being the one that's visible (naturally). When you segue or present a viewController modally you are adding a viewController to the stack or 'adding a piece of paper to your pile'. It is generally then a bad idea to segue to and from like you were doing because you are adding more and more 'pieces of paper' to the stack. You tend to get memory problems and/or irritating bugs :P. Instead what is considered best practice for programmatically going back is to do one of two things: if you are using a navigationController you should call popViewControllerAnimated, or if you aren't, like in this case, you should use dismissViewControllerAnimated. Both these functions take the topmost 'piece of paper' off the stack entirely. Fixing that problem of continually adding the viewControllers to the stack. If any of that was confusing (I'm writing this on my phone so could be!) just send me a comment or a message and I'll try to explain better! :)

touchesBegan delay after presenting UIView

I'm trying to load a UIView and then right away detect touches on that new view. Currently, overriding touchesBegan gives a delay of around a second.
So, I load up the UIView and immediately keep tapping on the screen. It takes around a second for touchesBegan to be called. From that point on, all is well. However, I can't afford the ~second wait initially.
I have stripped back all the code in the UIView to just the barebones incase anything was clogging up the main thread, but the delay still persists.
How can I go about getting immediate feedback of touches from a newly presented UIView? Thanks.
-- EDIT BELOW --
I've been playing around with this for the past few hours. Even when creating a custom UIWindow and overriding sendEvent, the UITouchPhase gets halted when the new view is displayed. To begin receiving events again I have to take my finger off the screen and place it back on the screen. (I don't want to have to do this).
The problem seems to lie with the segue to the new view controller. When it segues, the touch phase is ended. If I simply add a subview to the current view controller, I see the desired functionality (i.e. instant responding to touch).
Given my newly presented view contains a lot of logic, I wanted to wrap it all up in it's own view controller rather than add it to the presenter view controller. Is there a way for me to do this and use 'addSubview` to present it? This should hopefully achieve the desired effect.
In the end, I created a custom view controller with it's own xib. Where I would have segued, I now instantiate that custom view controller and append it's view. This has eliminated the touch lag.
Have you disabled multi-touch? There's an inherent delay while the controller waits to see if there's a follow up touch (on all single touches). The initial sluggishness might be from loading up the multi-touch code and deciding what to do about it.
myViewController.view.multipleTouchEnabled=NO;
As to your final question, look into view controller containment. Since iOS 5 Apple has provided the hooks officially and safely to present one view controller as a sub view of another.
Sadly I've no insight as to the greater issue.
I found an answer that worked for me from a similar question asked here:
iOS: Why touchesBegan has some delay in some specific area in UIView
This solution isn't check-marked on that thread, so I'll copy it here to make it easier to find.
override func viewDidAppear(animated: Bool) {
let window = view.window!
let gr0 = window.gestureRecognizers![0] as UIGestureRecognizer
let gr1 = window.gestureRecognizers![1] as UIGestureRecognizer
gr0.delaysTouchesBegan = false
gr1.delaysTouchesBegan = false
}

Resources