Unwind segue to view controller containing SpriteKit's SKView is extremely slow - ios

I've just started playing around with SpriteKit, and this issue pretty much immediately cropped up. If I have a view controller whose view is an SKView, pushing another view controller on top of it (say, a pause menu or an end game menu) and then unwinding back to the view controller with the SKView takes a noticeable amount of time, in my experience on a 4S and a 5, about a second and a half.
You can test this using the default Hello World template. I set it as the root controller of a navigation view and stuck a button in the navigation bar to trigger a push segue to a new view controller. The second view controller contains a button which triggers an unwind segue. When the button gets pressed, it stays highlighted for about a second and a half, and then finally the segue happens, which is incredibly jarring to the user.
I've glanced through the SpriteKit documentation and didn't notice anything written about the proper use of segues, is this just a bug or is it considered bad practice to push new views on top of an SKView? Instead, should I be using SKNodes/SKScenes to present my pause and end game menus, thereby always keeping the SKView on screen?

I had a similar issue and found this helpful. I was planning on using multiple UIViewControllers to navigate to different sections of my game like a menu screen, game over screen etc. but it was painfully slow. After refactoring my app to use a single UIViewController whose view is of type SKView, and creating a new scene for each screen I wanted, it started to work properly. At first I thought that this would be a pain because the controller would end up getting unmanageable, but found that most of logic was incapsulated in each SKScene and the controller was used to present new scenes only.
Also, you should note that adding UIKit controls to the SKView from an SKScene is acceptable. This is what I am doing to avoid having to reinvent the wheel every time I need a UIScrollView, UIButton or any other UIKit control. Of course you will have to remove the UIKit stuff you don't want to share between scenes when you transition.

Could it be possible that the SKView (or the scene) deallocates while the menu storyboard is presented? That would explain the delay when returning to the scene. Set a breakpoint in the corresponding dealloc messages of custom view/scene subclasses or use Instruments to check for the view and scene being among the live objects while the menu is presented.
Another possibility could be textures being removed from memory and then having to be reloaded. There could be an automatism in Sprite Kit, hard to say.
Try changing the way your storyboards are connected, for example have a "main menu" storyboard before the sprite kit view. It would be interesting to see if you get the same effect entering the view (repeatedly) as you do going back to it.
FWIW I only did a few tests with SK and storyboards but haven't noticed such a delay.

Related

Segue from SKScene to storyboard creating instances of old scene

Here is my storyboard showing a segue to a UINavigationController:
I fade out my game scene and call this segue from my SKScene like so:
mainVC?.performSegue(withIdentifier: "tySeg", sender: nil)
The nav controller moves a UIViewController on top of the game scene, as expected. But during the transition, I can see two new instances of the SKScene popping up in the background before the transition finishes and they are covered up by the new view controller. A print statement in my scene's didMove(to view:) method confirms that an SKScene object is appearing twice.
I can't figure out why my SKScene subclass is automatically being called twice after it triggers a segue. I also tried presenting the view controller like so but had the same issue:
mainVC?.present(navVC, animated: false, completion: nil)
I have a cludgy workaround in mind but I'd rather understand what is causing this to happen so I can prevent it. Any ideas?
UPDATE:
I suspect the reason has something to do with a passage from this Apple doc:
When presenting a view controller using the
UIModalPresentationFullScreen style, UIKit normally removes the views
of the underlying view controller after the transition animations
finish. You can prevent the removal of those views by specifying the
UIModalPresentationOverFullScreen style instead. You might use that
style when the presented view controller has transparent areas that
let underlying content show through.
When using one of the full-screen presentation styles, the view
controller that initiates the presentation must itself cover the
entire screen. If the presenting view controller does not cover the
screen, UIKit walks up the view controller hierarchy until it finds
one that does. If it can’t find an intermediate view controller that
fills the screen, UIKit uses the root view controller of the window.
You can see in my screenshot that GameViewController is my initial view controller. This is where I am calling the segue from. Like the doc says, maybe UIKit is removing the underlying content (the SKView that is presenting my game scene) when the segue is called. But I am using a full-screen presentation style, and UIKit requires that the scene's view controller must cover the screen. Since it was removed by UIKit, then UIKit goes up the view hierarchy and finds GameViewController which it calls to display.
I'm making a lot of assumptions, but seems like that might explain why my game scene is being recreated twice (or once... I had different results in my testing) while it calls a segue and waits for the transition to finish.
Also, I noticed that if I change the segue I'm using from Show to Present Modally, Over Full Screen, then the issue does not occur. That seems to support my guess.
Well, after our conversation it turns out that it uses the same instance of GameViewController.
So in your case you just workaround it. It's happening because your second view controller somehow not covering the entire screen (or it has opacity), and the system layout again the GameViewController. As you quoted:
When using one of the full-screen presentation styles, the view
controller that initiates the presentation must itself cover the
entire screen. If the presenting view controller does not cover the
screen, UIKit walks up the view controller hierarchy until it finds
one that does. If it can’t find an intermediate view controller that
fills the screen, UIKit uses the root view controller of the window.
For others i'll keep some of the old answer:
When you segue to a view controller you always creates a new instance. (So if you want to reuse the instance, don't use segues!)
When you present, you always shows it modally.
When you show you move to another view controller.

Toggling what is viewed when using an app

I am fairly new to swift and iOS programming, and I've been watching video tutorials of people making things visible and not visible. When it comes to large scale projects I am start to doubt that they have one million features toggling between visible and not visible on the tiny screen.
So my question is, is I am making a tiny app how do I transition from basic view such as logging into a game and changing the login view to actually ingame. How do these type of things work?
What you're asking is the basics of programming on Apple platforms. This can't be simply explained in a single answer. Please, start reading this tutorial from Apple to get the understanding of the MVC concept and storyboards.
In short. You use the storyboard in Xcode to build your screens. Each screen is a scene in your program and represents a view controller. The view controller organizes how data is displayed on a screen and how to react to user input. You can create view controllers in code in Xcode and link these to the scenes (that are also view controllers) in the storyboard. In the code you can program the behavior of the different views that you put in each scene. Moving from onze scene to another is done with segues.
A segue can be made programmatically, you drag iT from onze view controller to the other. Or, in the storyboard boy dragging it from, e.g., from a button to the next view controller. You give the segue na identificeren and in prepare for segue (a method) you can program what data needs to be transferred from the existing to the destination view controller.

SpriteKit Game slows down

I am writing an iOS app. It has a tab bar controller and one of the tab displays a view which has a collection view - it lists different mini games.
When I tap on the particular item, I show a view which consists of a both UIView and SKView. SKView presents the SKScene where most of the game play happens. There is a close button which closes this view and user is back to collection view.
Now everything works fine for first few iterations - Tap on a item in collection view, it shows a game and can play without hassle and close it. Animations are smooth. But if I just keep opening and closing this view, soon it becomes sluggish and animations are slower and slower. It also makes other parts of my app sluggish. I am clearing SKScene, deleting its children, also removing their actions. Instruments don't show any retail or memory leaks. Not sure whats going wrong. Please help!
Okay, answering my own question. NEVER EVER use NSTimer in SpriteKit. That was the problem in my case. Replacing it with update method fixed things for me.

Swift: how to implement additional view controllers with sprite kit game?

Ok, so I'm a beginner here and I am creating a game with Swift in sprite kit.
So far in my storyboard I have the initial View Controller and the Game scene.
I know that my game scene is implemented via the GameScene.swift file, i.e whatever code I write in GameScene.swift affects and correlates to the Game scene.
This is all fine, However I do not understand where I can write code that alters the initial view controller.
I would have assumed that the view controller would be affected by code written in the GameViewController.swift file, however when I try to connect labels and image views from my storyboard to GameViewController.swift by pressing control and dragging, nothing connects. Thus, I have no way of implementing the initial view controller.
Similarly, I cannot implement any additional view controllers that I create in my storyboard.
I can put labels and image views and everything into my view controllers, but I can't connect them to any file to make them do anything.
How do I do this?
It sounds like you have 2 different screens in the storyboard. Each view in a storyboard needs a separate view controller. To connect the ViewController.swift, you need to select the view in the storyboard, and in the identity inspector, make the class the viewController.

Keep a UIView or UIViewController on top of all others

I am subclassing UIApplication to intercept and display touches in my TouchDisplay view. I would like to extend the Application, Window, Delegate or Main ViewController in order to keep my TouchDisplay view on top of all other views. As my and most other applications work, views and controllers are added and removed all the time. I figure the correct answer will be able to deal with these additions and removals and stil keep the TouchDisplay view on top.
Thanks for your help,
Joe
Here are a few approaches you could take for this:
If you're targeting iOS 5+ and iPad only, you can make a top-level view controller which has two contained view controllers. The first would be a view controller for your "TouchDisplay" view. The second would be the application's normal root view controller. (i.e. your existing main view controller; you'll need to set definesPresentationContext to YES on this view controller) Since you're writing the container view controller, you can order those two subviews however you like. There is a WWDC 2011 Talk on view controller containment that goes into great detail about this. This is the most "correct" approach IMHO, because it gives you a view controller for your TouchDisplay view, handles rotation and generally plays nice with others. (This only works on iPad, because on iPhone a new modal view always covers the full screen.)
A more straight-forward approach is to simply add your TouchView to your existing top-level UIWindow as a subview with addSubview:. Most applications don't actually remove the top-level view controller or add new top-level ones; they just present other view controllers from it. A view you add in the top-level window will stay above those. Of course, your app may not follow this rule, in which case you might try option #3 instead. This has rotation gotchas (your view will not auto-rotate when the device rotates, so you need to do this yourself.) You could also force your view back to top, say, on a 1-second timer, if you are having issues with other things covering it. This is also not as nice as option #1 because you don't get a UIViewController, just a UIView.
The most extreme approach is that you can create another UIWindow and give it a higher window level, such as UIWindowLevelAlert and put your TouchDisplay view in that. You can then make the window background transparent, and it will stay above your normal app content. There are lots of gotchas here, especially about auto-rotation and which window is the keyWindow (which is why you should use #1 or #2 instead if you can).
After some time I was able to get my app working. I have made an easy to use overlay that shows touch feedback over your existing application.
You can download the project here:
https://github.com/megaplow/FingerTracks/tree/master/FingerTracks
Happy coding,
Joe

Resources