How do I present a View Controller and dismiss all others? - ios

I have about 20 View Controllers, chained together with Modal and Push segues. Now, at the last View Controller I want to switch back again to the first View Controller, as if the user has restarted the app. Unfortunately when I do this with
[UIViewController *viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"InitViewController"]];
[self presentViewController:viewController animated:YES completion:nil];
all of the previous view controllers are not unloaded. Not a single viewDidUnload method is called. How can this be done?

The instantiateViewController method creates a new copy of your view controller. Your existing view controllers aren't unloaded because iOS doesn't know that you want to 'go back', so to speak. It can't unload any of your existing view controllers because they're still in the navigation hierarchy. What you really want to do is 'rewind' your storyboard in some way.
Fortunately from iOS 6 there's a much improved way to do this, through unwinding. This lets you 'backtrack' in your storyboard right back to the start, which it sounds like you want to do. The WWDC videos have some examples and walk throughs, and you might also want to look at this existing SO question:
What are Unwind segues for and how do you use them?

I found that it can be done easily by calling dismissViewControllerAnimated:completion: on the first view controller in the hierarchy. Fortunately that's all it is needed to accomplish what I wanted :-)

Related

Does presentViewController clean up the parent view?

Here is my issue.
The App I am creating has non-linear navigation.
So I am implementing my own back button and doing my own navigation.
However, I am wondering how I should be presenting the next view.
If my navigation was linear, I could do:
-(IBAction)btnBackPressed:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
However that will not work for me since pressing back may not necessarily bring you back to the previous view.
Therefore I am thinking of using for example:
AddTaskViewController *add = [[AddTaskViewController alloc] init];
[self presentViewController:add animated:YES completion:nil];
The reason I am not using Storyboard is because all my UI is made programmatically in code.
The worry I have with this is that I think presentViewController will just push the new view on top of a stack. Thus, if the user presses back, forth, back forth, he will eventually run out of memory.
Given my circumstance that I need non-linear navigation and all my UI is created in code, what should I use to present the next view without wasting memory?
Thanks
If you are still interested in a clean solution using UINavigationController, consider this design.
Situation: Let A and B be types of view controllers. A is the root, and B is a detail view which can push or pop to other B controllers.
Goal: We want to delete any B controllers that are not adjacent to the currently presented view controller, but maintain the hierarchy so we can recreate the views when necessary. Thus, the maximum hierarchy the navigation controller will know about is A--B--B.
Design: Make A the navigation controller's delegate. Give it an array of model objects which represent B controllers enough to recreate the views from them. Add to this array whenever a B controller is pushed, which A will know about from the navigation controller delegate methods. Remove objects from the stack when a B controller is popped.
On pushing a B controller, the A controller will take the navigation controller's view controller stack and (if it exists) remove the B controller directly before the one that was displayed before the push. On popping a B controller, the A controller will (if it exists) recreate the B controller directly before the destination controller and insert it in the stack.
Example: Let's say A has kept track of a hierarchy like this: A--B1--B2--B3--B4. By the system outlined above, the navigation controller only knows about A--B3--B4. When the user pops B4, the A controller will be notified and recreate B2, inserting it before B3. Thus, the new hierarchy is A--B2--B3. When B5 is pushed from B3, B2 is removed to produce A--B3--B5.
I realize this is a fairly complicated system to implement, so I would only recommend it if you had a large number of controllers and required the amenities of UINavigationController. Another solution that occurs to me is UIPageViewController, which allows you to provide view controllers on the fly.
Hope this helps!
Yes you are correct that the user will eventually run out of memory if you keep presenting Modal view controllers using
[self presentViewController:add animated:YES completion:nil];
and
[self dismissViewControllerAnimated:YES completion:nil];
will only dismiss the top most viewController being displayed.
Instead you would be better off building your own container view controller and handle your own navigation. For more information read the "Implementing a Container View Controller" section in the UIViewController Apple documentation.
https://developer.apple.com/library/ios/Documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html

Conventional way to implement master-detail view controllers in iOS

I just have a quick question about recommended ways to implement a master-detail view hierarchy in iOS--the kind where selecting a row in a table on one screen pushes a details view for that item onto the navigation stack.
Specifically, should I reuse the same instance of the details view controller and just change its target and reload it each time, or should I instantiate a new instance of the view controller each time?
I'd prefer the first method, as it just seems generally more efficient, but I'm having trouble figuring out how to set the target and do the reload (especially the first time, when the view controller has not yet even been initialized--I'm using storyboards and that pretty much handles all of the initialization itself).
Or perhaps instead of setting the target on the child view controller, I could set it on the parent, such that each time the child view controller is shown, it reloads itself based on the parent selection? That actually sounds like the best bet so far, just looking for tips/warnings from anyone who's run into this before.
First, there's nothing wrong with creating a new view controller each time. If you use segues, that's what you'll get, since segues always instantiate new controllers. The detail controller will be deallocated when you pop or dismiss it anyway, so it won't persist.
If you want to use the same controller, you have to do your push or presentViewController in code. You can still setup the controller in the storyboard. Give it an identifier, but don't connect it up with a segue. In code, you check for the existence of your controller (you'll need a property for it), and if it doesn't exist, create it.
if (! self.detailController) {
DetailController *dvc = [self.storyboard instantiateViewControllerWithIdentifier:#"MyIdentifier"];
}
self.dvc.whateverProperty = self.somePropertyIWantToPass; // pass some date to it
[self.navigationController pushViewController:dvc animated:YES completion:nil];

iOS Showing views conditionally

I have a view in which a user selects an action to take and on that next screen there is a save and a back button. For both of the buttons the last line is dismissViewControllerAnimated:.
I need a way to make the 1st screen show only if the back button is used. save should send back to the main screen/rootViewController I am fairly new to iOS but not programming in general and just need a nudge in the right direction.
Could I set a bool flag to show or not? Maybe I can set the Tag on the view and then check that in the other screens on save/back? I assume I can check the parent view.
Sorry if this is a dup but I cant find anything specifically for this.
EDIT: I am not using a nav controller and am showing the views modally.
The answer will vary depending on how your UIViewControllers are structured and setup. If you're using a uinavigationcontroller then you can POP to the root view controller using:
[self.navigationController popViewControllerAnimated:YES];
If you're presenting your UIViewControllers modally, you can try to dismiss the presenting View Controllers of your modal view controller using the presentingViewController property:
[[[self presentingViewController] presentingViewController] dismissViewControllerAnimated:YES completion:nil];
You may also want to take a look at Unwind Segues if you're using a Storyboard:
What are Unwind segues for and how do you use them?
Finally, as far as determining whether the back button is pressed or another button - that depends on how the app is setup. You'll need to use your own logic (probably if / then statements or case / switch) to determine which button was pressed. You also may want to check out the sender argument in IBActions.
John, to have a UINavigationViewController return to it's root viewcontroller, you use:
[nameOfNavController popToRootViewControllerAnimated:YES];
The other guys are correct that the information you've provided is definitely not enough to determine exactly what you need to do.
You can use the presentingViewController property of a modal view controller to access it's presenting controller.
It turns out that I was using the terminology wrong. I am presenting all views modally and that is the issue, there is no navigation controller. I ended up using NSNotification to build a listener and had the main view controller listen and then dismiss the view and hence show itself. Worked a treat.
here is the link to the code I ended up with.
http://iphonedevsdk.com/discussion/114737/view-heirarchy-issues-possibly-from-the-camera
Hopefully this helps someone else.

Manipulating view hierarchy by code

If I have a UITabBarController (2 tabs) as root view controller then in the first tab (FirstTabViewController) I init a UINavigationController with a root view controller AddReminderViewController and present it.
Then inside AddReminderViewController I present another UINavigationController with a root view controller called ChooseOptionViewController and present it.
Now when I'm inside ChooseOptionViewController I want to programtically go back to FirstTabViewController how can I do this as easy as possible? Do I need to - dismissViewController... on all view controllers that I have presented or is there an easier way?
Also inside ChooseOptionViewController how can I find out the class that presented ChooseOptionViewController? I tried doing [self.presentingViewController class] but that just says UINavigationController (not AddReminderViewController)
Now when I'm inside ChooseOptionViewController I want to
programtically go back to FirstTabViewController how can I do this as
easy as possible?
You really ought not to have the ChooseOptionViewController try to manage all that by itself. It should simply tell its parent that its job is done and let the parent dismiss it. The parent could then tell its parent that its job is done, and so on. This approach will make it much easier to maintain your code, and to change things around when you decide that's necessary, without breaking ChooseOptionViewController.
For example, imagine that AddReminderViewController wants something else to happen, like presenting ChooseMoreOptionsViewController after ChooseOptionsViewController has been presented. If AddReminderViewController is in charge of the flow of its part of the program, that's easy. If ChooseOptionsViewController knows enough about the reset of the app to dismiss view controllers all the way back to FirstTabViewController, you'll have to modify it every time there's a change in the flow. That's not a recipe for long term success, and it adds a lot of unnecessary and unhelpful complexity.
generally unless an exception you should use only one navigation controller.
keep pushing view controllers onto it.
in that way you can move pop top to root view controller.
for ex:
1
just add one navigation controller to tab 1
2
setrootviewcontroller of navigation controller to FirstTabViewController
3
from FirstTabViewController you can push AddReminderViewController(using the same navigation controller)
ex:- [self.navigationController PushViewController:....];
4
from AddReminderViewController you can push ChooseOptionViewController(using the same navigation controller)
ex:- [self.navigationController PushViewController:....];
5 finally use [self.navigationController popToRootViewController];

dismiss launched viewControllers and return to initial view controller (storyboards)

I've seen this question a couple of times but never really answered. I'm wondering if there is an acceptable/clean way to dismiss all launched view controllers and return to the initial view controller when using storyboards (say from an action within a spawned view controller).
I know how to use delegates, but, I'd prefer to not have my initial view controller implement delegates for every possible spawned view controller. Instead, I'd just like a home button that cleans everything up and returns to the initial view controller from anywhere in the app.
Thoughts?
EDIT: Just for clarity, assume I am NOT using UINavigation Controllers.
EDIT2: Is it possible to just access the methods of the "initial view controller" from anywhere in the app like you might do with the appDelegate?
This should do it at any point. Just stick it in an IBAction and hook it up to a button :)
[self.navigationController popToRootViewController];
I ended up using a singleton. Seems to work quite well.
On the initial load of the initial view controller, I set the view controller as the singleton's property. I can then execute the following code in any action method on any view controller in the app to dismiss all view controllers and return to the initial view controller.
initialViewControllerManager *ivcManager = [initialViewControllerManager sharedInstance];
LPViewController *ivc = ivcManager.initalViewController;
[ivc dismissModalViewControllerAnimated:YES];
May not be the "right" answer, but, seems to work. And, given the complexity of my scenes, relying exclusively on UINavigationControllers would be very complicated.

Resources