Warning: Attempt to present view controller on another view controller whose view is not in the window hierarchy - swift

I have a working simple single player game, where the initial view controller has a button to start the game. This button performs a segue and all game logic in the GameViewController is working as expected.
I've followed this tutorial to add multi player functionality to my game.
On the initial view controller, a button now calls
GameKitHelper.sharedGameKitHelper.findMatchWithMinPlayers(2, maxPlayers: 2, viewController: self, delegate: MultiPlayerNetworking)
}
which has the following implementation in GameKitHelper.swift:
func findMatchWithMinPlayers (minPlayers: Int, maxPlayers: Int, viewController: UIViewController, delegate: GameKitHelperDelegate) {
matchStarted = false
let request = GKMatchRequest()
self.delegate = delegate
request.minPlayers = 2
request.maxPlayers = 2
presentingViewController = viewController
presentingViewController.dismissViewControllerAnimated(false, completion: nil)
let mmvc = GKMatchmakerViewController(matchRequest: request)
mmvc?.matchmakerDelegate = self
presentingViewController.presentViewController(mmvc!, animated: true, completion: nil)
self.delegate?.matchStarted()
}
The Class MultiPlayerNetworking implements the GameKitHelper protocol, and gets called on the matchStarted function.
The MultiPlayerNetworking class in essence takes over here, and starts sending out messages to hosts and remote players.
Note that some time later, When auto-matching finishes, the following function gets called in GameKitHelper:
func matchmakerViewController(viewController: GKMatchmakerViewController, didFindMatch match: GKMatch) {
viewcontroller.dismissViewControllerAnimated(true, completion: {})
self.match = match
match.delegate = self
}
Now, I think this says that the GKMatchmakerViewController is dismissed, thereby showing me the initial view controller again (and this is what happens on screen).
Now my issue! After the GKMatchmakerViewController is dismissed, I'm back at the initial view controller and want to 'simulate' an automatic segue to my gameView (which has logic to deal with a multi player game as well).
I've made the initial view controller conform to the MultiPlayerNetworking protocol, which has a function to simulate a segue:
func segueToGVC() {
self.performSegueWithIdentifier("game", sender: nil) // self = initial view controller
}
However, xCode complains with:
Warning: Attempt to present <GameViewController: 0x7d440050> on <GKMatchmakerViewController: 0x7c8fbc00> whose view is not in the window hierarchy!
I'm stuck here, and have tried so many different methods of dismissing the view controller, to making sure I'm calling the performSegue function on the topViewController via this link, but nothing works.
My question: why is the GKMatchmakerViewController visually dismissed, but still present in the view hierarchy, such that calling a performSegue function on the initial view controller give the above error/warning?
Views are greatly appreciated!

why is the GKMatchmakerViewController visually dismissed, but still present in the view hierarchy
Here are two suggestions:
Perhaps it's because dismissal takes time. You are saying:
viewcontroller.dismissViewControllerAnimated(true, completion: {})
So there's an animation. Don't attempt to perform the next segue until the animation is over.
Perhaps you are just wrong about who self is. You are saying:
self.performSegueWithIdentifier("game", sender: nil)
// self = initial view controller
We have only your word, in that comment, for who self is. Meanwhile, the runtime seems to think differently about the matter:
Attempt to present <GameViewController: 0x7d440050> on <GKMatchmakerViewController: 0x7c8fbc00>
It might be good to believe the runtime; after all, it knows more than you do.

Related

Swift Modal ViewController. Is there a easy way to find the result of the ViewController

I have a View(ViewController1) that overlays a modal ViewController2.
ViewController2 has three options
Delete all sessions
Select a place
Cancel
It's fairly easy to get to launch the modal viewController as this in ViewController1
#objc func presentMenu() {
let overlayController = ViewController2()
overlayController.transitioningDelegate = self
overlayController.modalPresentationStyle = .fullScreen
present(overlayController, animated: true)
}
This basically shows ViewController2. However viewController1 code has gone past this and where do I find out that this ViewController2 has been dismissed?
You could implement viewWillAppear and try to figure out that the reason you're appearing is that the presented fullscreen view controller is being dismissed. But it would be even better to use the standard pattern where the presented fullscreen view controller tells you that it is being dismissed. To make that possible, it has a delegate, which you make sure is you:
overlayController.modalPresentationStyle = .fullScreen
overlayController.delegate = self
present(overlayController, animated: true)
Then the overlay controller calls a known method in its delegate when it is about to be dismissed:
self.delegate.dismissalIsHappening()
That method is usually defined by way of a protocol, which is why this is called the protocol and delegate pattern. I have not shown you the entire pattern! If you search on that term, you'll find lots of complete examples.

swift xcode iOS: can I re-use a loaded modal fullscreen view controller?

I have a storyboard with two view controllers. First one, VC_1, has one button that opens 2nd one - VC_2.
VC_2 also has a button that opens VC_1.
Both controllers have almost identical code:
class VC_1: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
print(“VC_1 loaded")
}
override func viewDidAppear(_ animated: Bool){ print(“VC_1 appeared") }
override func viewDidDisappear(_ animated: Bool){ print(“VC_1 disappeared") }
#IBAction func btnShowVC_2(_ sender: UIButton)
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
secondVC = storyboard.instantiateViewController(identifier: “VC_2”)
secondVC.modalPresentationStyle = .fullScreen
show(secondVC, sender: self)
}
}
The difference is only in "VC_2" instead of "VC_1" in the 2nd controller code.
I have seen this View Controller creation code in Apple documentation and many other examples around the Internet.
When I press the button on the VC_1, I see in the debug window, that VC_2 is loaded and appeared, and VC_1 is disappeared. And same, of course, happens when I press the button on VC_2 - it disappears, and VC_1 is loaded again.
My questions are:
what happens with View Controller object after "viewDidDisappear" has been called? Does it really disappear from memory, or "disappear" only means "you cannot see it on the screen?". I do not see "viewDidUnload" in the documentation...
I suppose that "viewDidLoad" means that new View Controller object was created in memory. Is there any way to load the View Controller object only once, and then hide and show it without causing "viewDidLoad" to be called? I tried to do it with global variable "secondVC" but got "Application tried to present modally an active controller" error.
viewDidDisappear: called after the view is removed from the windows’
view hierarchy. No, View controller object just left the view property. By the way the amount of memory used by view controllers is negligible. So dont think about too much. If you want to catch when Your View controller object release from the memory put
deinit { print("vc deallocated") }
viewDidUnload, it has been deprecated since the iOS
6, which used to do some final cleaning.
Partly true. Keep in mind ViewDidload called one time for the life cycle of view controller. There is a method called before viewdidload but this is not related with your question.
In addition to "There is a method before viewdidload" -> loadView( ) is a method managed by the viewController. The viewController calls it when its current view is nil. loadView( ) basically takes a view (that you create) and sets it to the viewController’s view (superview).

Accessing UINavigationController from rootVC Subview (subview loaded from Nib)

The main ViewController is embedded in a UINavigationController subclass, and the VC has a subview that is loaded from a nib. The subview is called MenuView, and contains UIButtons that will link to other VCs.
To keep my main ViewController less unruly, I have put all these buttons into a subview that loads from a nib that animates the menu opening and closing.
However, I would like to present other view controllers from these, sometimes "Modally", sometimes "Show". What I have done seems to work, but I just want to know if this is alright, or if I have caused some unwanted effects that I'm unaware of (like a strong reference cycle that would cause a memory leak, or something). Or is there a better way to do this?
Some code:
In MenuView.swift
class MenuView: UIView {
var navigationController = CustomNavigationController()
func combinedInit(){
NSBundle.mainBundle().loadNibNamed("MenuViewXib", owner: self, options: nil)
addSubview(mainView)
mainView.frame = self.bounds
}
#IBAction func optionsAction(sender: AnyObject) {
self.navigationController.performSegueWithIdentifier("presentOptions", sender: self)
}
In ViewController.swift
menuView.navigationController = self.navigationController as! CustomNavigationController
Short answer: No, it is not alright to access a view controller from within some view in the hierarchy, because that would break all the MVC rules written.
UIView objects are meant to display UI components in the screen and are responsible for drawing and laying out their child views correctly. That's all there is. Nothing more, nothing less.
You should handle those kind of interactions between views and controllers always in the controller in which the view in question actually belong. If you need to send messages from a view to its view controller, you can make use of either the delegate approach or NSNotificationCenter class.
If I were in your shoes, I would use a delegate when view needs some information from its view controller. It is more understandable than using notification center as it makes it much easier to keep track of what's going on between. If the view controller needs some information from a view (in other words, the other way around), I'd go with the notification center.
protocol MenuViewDelegate: class {
func menuViewDidClick(menuView: MenuView)
}
class MenuView: UIView {
var weak delegate: MenuViewDelegate?
#IBAction func optionsAction(sender: AnyObject) {
delegate?.menuViewDidClick(self)
}
}
Let's look at what's going on at the view controller side:
class MenuViewController: UIViewController, MenuViewDelegate {
override func viewDidLoad() {
...
self.menuView.delegate = self
}
func menuViewDidClick(menuView: MenuView) {
navigationController?.performSegueWithIdentifier("presentOptions", sender: self)
}
}
For more information about communication patterns in iOS, you might want to take a look at this great article in order to comprehend how they work.

UITransitionContextFromViewControllerKey is returning nil

So, I am using the Elastic Transition pod (Cocoapods), and when I transition my app crashes because the UITransitionContextFromViewControllerKey key is nil. I am really confused as to why that value would be nil. What are some probable causes and solutions to resolving this error?
So here is some of my code for when I am transitioning into the next view controller:
func handleTap(sender: AnyObject) {
transition.sticky = true
transition.transformType = .TranslateMid
transition.showShadow = true
transition.edge = .Left
transition.startingPoint = sender.center
performSegueWithIdentifier("mySegue", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
segue.destinationViewController.transitioningDelegate = transition
segue.destinationViewController.modalPresentationStyle = .Custom
}
Then, when it is trying to transition my app crashes complaining that this line of code (in Elastic Transition) is returning nil:
transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
According to the ElasticTransition Github page, if a view controller is presented modally with elastic transition, then the destination view controller's transitioningDelegate needs to be set to ElasticTransition and modalPresentationStyle to .Custom. However, if the view controller is pushed onto a navigation controller stack using the elastic transition, then only the navigationController?.delegate needs to be set to ElasticTransition.
After some chatting, #Harish told me that he uses a push segue. However, the code in prepareForSegue is the setup code for when one presents a view controller modally. That is likely the reason why UITransitionContextFromViewControllerKey returns nil. So I believe the solution is to set the navigationController's delegate to ElasticTransition somewhere, and remove the code in prepareForSegue.

Segue stops working after first segue

I am trying to use a custom segue, that makes the view 'scroll' to the right or left, when a button is clicked. I added a custom class that looks like this
class horizontalSegue : UIStoryboardSegue {
override func perform() {
var oldView = self.sourceViewController.view as UIView
var newView = self.destinationViewController.view as UIView
oldView.window?.insertSubview(newView, aboveSubview: oldView)
newView.center.x = oldView.center.x + oldView.frame.width
newView.center.y = oldView.center.y
UIView.animateWithDuration(0.6, animations: { newView.center = oldView.center }, completion: { finished in Void })
}
}
but the problem is that after I segue once, I cannot segue back to the view I segued from. I think its because of the way I have the oldView and newView declared, the app isn't updating which view is which, so its segueing back to the current view when I try to segue to the first view.
If I am correct, how would I make sure the app updates which view is which?
Apple documentation says: "Regardless of how you perform the animation, at the end of it, you are responsible for installing the destination view controller (and its views) in the right place so that it can handle events. For example, if you were to implement a custom modal transition, you might perform your animations using snapshot images and then at the end call the presentModalViewController:animated: method (with animations disabled) to set up the appropriate modal relationship between the source and destination view controllers."
May be you should add something like
[self.navigationController pushViewController:vc animated:NO]
into your animation's completion handler?