iOS View Controller Containment Parent/Child calls dance - ios

I am trying to make a container view controller that works similarly to navigation controller. When I add something to the stack what do I do with the view controller that is a already there?
It is still my child but I don't want it's view in the view hierarchy. Should I call removeFromParentViewController on it, and just keep a separate stack with it, in that stack? So when the view above is popped off, I can check what view I should push back in order to go back to previous one.
Or should I just remove its view, without removeFromParentViewController call, and add another child controller, and its view to container view hierarchy?
Basically what do I do with the controllers that aren't on the screen?

The "stack" is just an array that a navigation controller uses to keep track of its view controllers. If you're building your own, you will need to have an array also. The way a navigation controller works, when a controller is pushed, that controller is added to the array, and if one is popped, that one is removed from the array. When you do a transition, the one that's going off screen should call removeFromParentViewController, so it's no longer in the hierarchy (but if it's going away because of another one being pushed, you would leave it in your array -- that's how the controller knows which one to go back to on a pop). You should use transitionFromViewController:toViewController:duration:options:animations:completion: to do your transitions from one controller to the next.

Related

setViewControllers vs removeFromParentViewController

We are building a journey where we need a View Controller to remove the Controller that came before it in the stack.
When we land on that View Controller it will be the 3rd controller in the stack and we need it to remove the Controller that is second (i.e. the Controller that is at index 1).
In ViewDidLoad we tried doing this by getting the current array of View Controllers from the Navigation Controller, removing the Controller at index 1 and then calling SetViewControllers. However when the back button is pressed we end up going back to the View Controller that was supposed to be removed.
If instead we call removeFromParentViewController on the View Controller to be removed then we get the behaviour we expected.
What are the differences between these two approaches? What would cause a View Controller to remain in the stack despite it not being in the array passed to SetViewControllers?

What is the best way to present a view controller on top of everything in iOS?

I'm trying to present a view controller that will be kept displayed above everything, regardless of the currently presented view controller, so it will be kept as displayed even if the view controller behind it will get dismissed, or starts to present another view controller.
I know how to find the topmost view controller (for example as suggested here) but in this case presenting over this view controller will make the new view controller depends on the hierarchy of the parent controller, which I try to avoid.
Present the view from the rearmost view, the window. But give it a high z index so that it shows up in front of everything else.

How to go back to the first view

Assuming i have a setup like the above, how would one go back to the first view (eg. The one with the Label) ?
I am asking this because i am not sure if is there a particular reason why there would be multiple navigation controllers setup similar to above.
I came across this problem because popToRootViewController will only go back to the first view of that navi controller. Eg. if i was at one the views of the 2nd navigation controller and i were to run popToViewController, it will only go back to the first view controller of the 2nd navigation controller. I have since solved the problem by removing the other navigation controller but my curiosity had to be resolved.
As I stated in the comment, the second view controller is almost certainly unnecessary (but this isn't entirely necessarily the case), so your best bet is most likely to simply to remove that second navigation controller.
With that said though, how you would get back to the original view controller with the existing set up would depend largely on how you got to the current view controller.
If the second navigation controller is pushed on to the first navigation controller's navigation stack, then from any view after the second view controller, you could do something like this:
self.navigationController?.navigationController?.popToRootViewControllerAnimated(true)
Otherwise, if the second navigation controller were presented modally, you could dismiss it from any of the view controllers in its navigation stack with the following code:
self.navigationController?.dismissViewControllerAnimated(true, completion: nil)
It's important to remember that UINavigationController is a subclass of UIViewController and as such, any method we can call on the latter can also be called on the former.
One simple (Hacky) approach is to maintain a global pointer to your first navigation controller (Maybe in the AppDelegate or .pch file), and whenever you want to go your root controller, you can just say dismissViewController:animated, and then popToRootViewController.
Should work in all the cases.

How to pass data to child controller when interactive pop gesture is cancelled in iOS7?

Traditionally the data is passed to a child controller by calling prepareForSegue, for example, when a table cell is clicked.
In iOS 7, there seems to be a new navigation idiom, where you drag the left edge of the screen to go back up the navigation stack. It seems like when you just start the gesture, child view is removed and parent is shown, BUT if you cancel it by not dragging far enough, it will be cancelled, child view snaps back into place, but there was no prepareForSegue call. The data item in the child view at this point is nil, which makes me think it's re-created.
How to properly pass data to child view controllers that would both work for segues and this navigation idiom?
You're wrong about the view controller that's being popped by the drag being destroyed, it's not. viewWillDisappear is called as you start to drag, and if it cancels, viewWillAppear and viewDidAppear are called but not viewDidLoad or dealloc, so no new controller is being created. If you want to pass data back to the controller that appears when you do the drag, you should use a delegate protocol. You can set that controller as the delegate of the one that's pushed when you do the original push (or the segue that goes forward to that second controller). Also, it's not really correct to call these controllers parent and child -- they're both controllers in the navigation controller's viewControllers array. The parent of either of those two controllers is the navigation controller.
The actual reason turned out to be completely unrelated to views lifecycle, but the fact that data items passed down to detail views were stored as weak references.
In my code the list view keeps its data in an NSArray* property.
On transition, detail view is initialised with an item from said array and stored in a weak property.
When pop gesture is initiated, list view is shown. In my code, when the list view is shown its item list is reloaded, naturally nulling references in the child view.
When the gesture is cancelled, the detail view is presented without segue, with nil item.
Solution: change references in details views to strong.

How to determine which view controller is currently active/the one displaying a view?

In my app I am queueing some local notifications, when they fire I must present a modal view. The trouble is I have numerous view controllers any one of which could currently be active and thus the one that needs to present the modal view controller. How can I determine which one is currently in use?
I am setting a navigation controller as the windows root view controller, and this can push any number of other view controllers, some of them themselves may also be currently presenting another view controller modally.
This must work on iOS 4 and 5.
I have a lot of view controllers so would like to avoid putting code in each of them to each check if they are currently the top one.
You can look at the navigation controller's topViewController property to find out which controller is at the top of the stack. This will be the one whose view is displayed.
Since you may also be presenting a modal view controller, you'll probably be more interested in the visibleViewController property, which will give you the controller for the current view whether its presented modally or pushed onto the navigation stack.
Create a variable that stores a pointer to the ViewController that was most recently pushed. Every time you push a new ViewController, update this variable. Then you'll always know which one is on the top!

Resources