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.
Related
There is a ViewController1 which has a stackView.
I created an instance of ViewController2 and added it's view as a subview to the stackView of ViewController1, I wanted to see if only by doing this does viewDidLoad of ViewController2 is invoked and it did so, ViewController2 viewDidLoad was invoked when I added view of ViewController2 to stackView of ViewController1. For ex: In ViewController1self.stackView.addArrangedSubView(viewControler2.view)
Then why do we need to do addChild(viewController2) then add view as subview, those typical lines which adds childController and it's view in parent view controller hierarchy
Certainly viewDidLoad was called. That happened instantly as soon as you referred to ViewController2's view in your code.
But let's say your ViewController2 does other things besides load a view. Suppose its view contains a button that is hooked through an action to a function in ViewController2. If you now tap that button nothing happens.
That's because the ViewController2 itself is dead: it has vanished in a puff of smoke.
You can see that by implementing deinit in ViewController2. You will see that, just as viewDidLoad is called, so is deinit. You are left with a view controller's view that has no view controller. That is bad.
There is a view controller hierarchy that is responsible for maintaining relations between view controllers. When you add ViewController2 as a child view controller of ViewController1, you maintain that hierarchy, and you maintain it correctly according to the rules, which say:
If VC2's view is somewhere inside VC1's view, then VC2 needs to be a child (at some depth) of VC1.
In other words, the view hierarchy and the view controller hierarchy must run together. Otherwise, the responder chain will break and life will become chaos.
(There are other requirements when you make one view controller the child of another, like sending didMoveToParent to the child as part of the opening dance, along with other message forwarding responsibilities later, so as to ensure that the child view controller gets other messages like viewDidAppear at the right time. It's a complex business. However, I've focussed my answer on the very basic part of what you asked.)
I should add: if your goal was merely to fetch a view out of a nib and stuff it into your own view, you can certainly do that, no problem. What you must not do is use a view controller as a kind of magnet or vacuum cleaner to fetch a view for you if your intention is then to let go of the view controller itself.
So I have a custom PresentationController which means I've set modalTransitioningStyle = .custom. This means it's not set to overCurrentContext or overFullscreen. It also means that definesPresentationContext is not is not applicable, since it's only used in those cases.
With this I present a custom popover-like view controller that takes like third of the screen.
Now the tricky part:
On this custom presented VC, I want to present another view controller with the same custom presentation that is larger (let's say half of the screen) than the presenting one.
Problem:
The frame of the second presented view controller is constrained to the size and location of the parent, so you will not see it entirely.
Even if I would manage to present it on a fullscreen view controller behind the first, the content would also appear behind, not in front of it.
So the question is: How do I present a view controller that has larger bounds than the presenting one using my own Presentation Controller?
I don't think any code would help here, but let me know if you think otherwise.
I haven't really seen any resource that gives a good and simple explanations on relationship among window, rootviewcontroller, childviewcontroller, navigationcontroller and how they piece together in iOS development. Anyone one knows how to put this in a easy-to-understand way or any online resource or book that does a good job in explaining it?
Per the documentation on UIWindow:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIWindow_Class/
A UIWindow object provides the backdrop for your app’s user interface and provides important event-handling behaviors. Windows do not have any visual appearance of their own, but they are crucial to the presentation of your app’s views.
Xcode typically provides your application's main window, but you can add more if you need to.
From the documentation link you can see that UIWindow is actually a UIView
Enter your first view controller. Like providing a main window, when you start a new Project in Xcode the project template usually wires up your initial view controller, which as the name implies controls a view (UIView).
You could call this initial view controller your RootViewController but if you get a handle on the UIWindow you could just as easily swap out the current initial view controller's view for any other view controller view you like.
That probably doesn't help with hard and fast rules for things, but if I understand what you are asking, RootViewController is likely the initial view controller for you application. For example, if you are using Storyboards, Xcode typically makes Main.storyboard, you will see a gray arrow pointing to the UIViewController representation.
This is pointing to the Storyboards Initial View Controller. You can verify this from the Attributes Inspector. Select the view controller then select the attribute inspector:
So that's basically RootViewController. ChildViewController is just any other view controller that is a child of a view controller.
I assume what you are referring to is:
addChildViewController:
removeFromParentViewController
willMoveToParentViewController:
didMoveToParentViewController:
You can read more about these methods here:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/
Under Implementing a Container View Controller
The quick gist of it is, A View Controller controls a view. View's can have subviews. Those subviews can come from other View Controllers. The methods outlined above pretty much just enable things like viewWillAppear, or viewWillDiappear to be called on the child view controller automatically when those methods are invoked on the parent view controller.
Per the docs:
By default, rotation and appearance callbacks are automatically forwarded to children. You may optionally override the shouldAutomaticallyForwardRotationMethods and shouldAutomaticallyForwardAppearanceMethods methods to take control of this behavior yourself.
a NavigationController is just like any other View Controller. It contains some special behavior for transitioning between views, but like other View Controllers it has a View (UIView) that it manages. A navigation controller could be your Initial View Controller / RootViewController just as any other View Controller can be, it all just depends on what you are trying to do. For example, a simple app that is just a list view, where you can tap an item and get a detail view could be constructed as:
1) Initial View Controller -> NavigationController
2) The NavigationController's first ViewController (Apple calls this a RootViewController) would then be a TableViewController.
3) Selecting a TableCell in the TableView (TableViewController manages a TableView) would then transition you to your Detail View Controller. The Navigation Controller knows how to do all that Sliding back and forth drama.
That's a pretty simplistic overview you can search the internet/youtube for more full featured tutorials outlining the same thing in more detail.
Example: https://www.raywenderlich.com/113388/storyboards-tutorial-in-ios-9-part-1
It's worth your time to do a few of these to get your bearings. Yes, it will likely cost you a few hours of your day. Take heart, everyone who ever started doing iOS development had to go though the same thing. =)
My intent was to create a master scene and view controller that was in control most of the time, but when logic demanded it would segue to one of several other scenes. Each of these "other" scenes would return to the master view controller using rewind segues. Which other scene was chosen was determined by logic in the master view controller. This worked fine until I found a need to transition from one of the "other" scenes to a different "other" scene without presenting anything from the master scene. I want to put a performSegueWithIdentifier in the master view controller before anything is presented.
I have tried putting the performSegue.. in the method that catches the rewind segue, in the viewWillAppear method, in a block kicked off async from the rewind method, but in all cases I see the performSegueWithIdenitifer method execute, then the appropriate prepareForSegue but then control returns to the master view controller and the scene I get in the device is from the master view controller.
I tried to look at other methods and it would seem that building a custom container view controller is one way of doing this, but it does not seem right since all the "other" scenes I want to segue to are push segues and take over the entire screen.
Is there some reason that a view controller that has received control from a rewind segue cannot then kickoff a segue without ever appearing itself? Is the Custom container view controller the correct way to do this? And if the latter, do I let the storyboard set up the embed segues to all the other view controllers, or do I leave them unconnected in the storyboard and load them in code?
Thanks in advance, ##warning - novice here - this is my first real project##
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.