Does presentViewController clean up the parent view? - ios

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

Related

presentModalViewController vs pushviewcontroller memory cosumption

I have two method to jump from one viewcontroller to another
For presentViewController
[self presentModalViewController:view animated:YES];
For pushViewControlle should use
[self.navigationController pushViewController:view animated:YES];
Which Will be best approach ?
Which will cause more memory leak?
Which one is use if Our Design is like
Introduction view (bunch of slides )-> login -> signUp-> HomeActivityScreen-> Then Bunch of tab bar in it
If you use pushViewController you will automatically get a "Back" button in the navigation bar. If you use presentModalViewController you do not, and generally will have to implement your own controls and/or callbacks to handle dismissing the controller.
Conceptually the modal presentation style is generally used for atomic tasks that you cannot navigate away from (i.e. you either complete the task, or you cancel, and you cannot do anything else within the app until you do one or the other).
If you're wondering why have the difference in the first place, I can't say. Personally I think frameworks that provide a unified API for moving from one controller to another (like cocos2d, or Android) make a lot more sense.
You use modal view controllers to focus the user's attention on a Task. When you push, the user is in some kind of navigation flow, but still has the total application at their fingertips. They might decide to go forward or backward, switch to a different tab in the middle, whatever. When they get a modal view controller, they can't do any of that until the task is completed or canceled out of (the modal view is dismissed)
When you present a modal view controller, the system creates a parent-child relationship between the view controller that did the presenting and the view controller that was presented. Specifically, the view controller that did the presenting updates its modalViewController property to point to its presented (child) view controller. Similarly, the presented view controller updates its parentViewController property to point back to the view controller that presented it.
Modal view controllers provide interesting ways to manage the flow of your application. Most commonly, applications use modal view controllers as a temporary interruption in order to obtain key information from the user. However, you can also use modally presented view controllers to implement alternate interfaces for your application at specific times.
So, from my understanding this one is best option.
[self.navigationController pushViewController:view animated:YES];
Apple handles the memory for both of these through Automatic Reference Counting. Although pushing a view controller might require more memory than presenting the same, the allocated memory is released when popping or dismissing the ViewController. ARC releases the said memory if no strong references are made from within the popped ViewController, or in other terms, the ReferenceCount is 0. So care must be taken so that no strong references are retained to the ViewContoller's objects.
Now whether to present or push a ViewController depends solely on your app design. Read this to get an outline on when to present a ViewController, .
They are two approaches to navigate from one view controller to other view controller.
By default:
- pushviewcontroller: you will have a back button to return to last visited page. your viewcontroller is put on top of the navigation stack. Think like a webpage when you navigate between pages.
- presentmodalviewcontroller: you have nothing to comeback to last visited page. It is usually used for: a pop-up or to change to new branch of navigation.
The use of pushviewcontroller & presentmodalviewcontroller does not produce memory leaks.
For your application flow, I suppose that HomeActivitiesScreen is one of viewcontroller in your "bunch of tab bar". If so, create a viewcontroller that contains that tabbar and make it as root of your application (named RootViewController for example). Then:
When application is launched, you show the RootViewController
And immediately, present the modal view to your "introduction pages", then login/signup. This modal view start with a Navigation view controller structure.
When the user is connected, dismiss your modal view, return back to your HomeActivitiesScreen and refresh contain if need.
Like this, you do not keep the reference to your login/signup screen when you do not need.

Moving to any of several view controllers from any of the others

The app I am developing has five view controllers.
Lets call them A, B, C, D, and E, with A as root view controller.
A will have four buttons to navigate to each of the other view controllers. Similarly, B, C, D, and E will have four buttons each to navigate to all other view controllers.
Is it a good idea to to use presentViewController: to implement the navigation, since there is no clear hierarchy in the relationship of the view controllers? I don't think I clearly understand the presented vs presenter relationship.
Does the dismissal of the presented view controller have to be handled by the presenter?
Suppose A presents B, and B then presents C, and C then presents A. Are any of the controllers released/dismissed? Who is handling whose dismissal? When A is finally presented, is B still in the memory?
The other approach I thought of is to design and write a View Controller Container and manage all the view controllers. I have read this is not an easy territory to walk on.
Which of these makes more sense?
You can use UINavigationController to push new controllers to the stack. If you don't want user to go back (it means not keeping the old view controllers in the stack) you can simply set the newly allocated view controller as the root vc:
-(void)buttonAPressed:(UIButton *)button {
AViewController *vc = [[AViewController alloc] init];
self.view.window.rootViewController = vc;
}
if your situation is that you have 5 instances of view controllers and you use just the same, you have to use the container solution. If you use the same instance, you have in each time allocated just this 5 and so you haven't problem.
If instead, you need to navigate in depth with new instance, and after came back (like a navigation controller) the best solution is instantiate each time a new view controller and when you came back to the previous, the last is dismissed.
N.B. If you need to open many instance in depth but potentially without come back (like a navigation controller) you need to use the first solution, because you have to use 1 instance for each view controller.

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];

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

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 :-)

IOS Navigation Non Heiarchical

I have an iOS app using iOS 5 and Xcode 4.3.2 that is made up of 7 view controllers. VC1 links to VC2, VC2 can link to VC3-VC7 and each of those controllers can link to each other (think of it as a side bar navigation). If I use segues the views are repeatedly added to the stack and if a user goes back and forth it can use a large amount of memory. How can I implement this navigation where I release the previous controller? They are all small controllers so loading them takes little time/processor/memory. Can I presentViewController and then release the presentingViewController somehow? Thanks.
If you implement a UINavigationController, you can use the push and pop view controller methods to go back and forth. popToViewController:animated: is described here, along with 3 other helpful methods.
Well seems like there should be no problem from VC1 to VC2. For the VC3 - VC7 you could:
Present as modalViewController instead of pushing that to the stack.
Or:
- Use the popToViewController:animated: function of your UINavigationController if the Controller is already present in the stack of controllers, otherwise push it. Like
// Assuming u need to push VC6
for(UIViewController *controller in [urNavController viewControllers]){
if([controller isKindOfClass:[VC6 class]])
{
[urNavController popToViewController:controller animated:YES];
}
else{
VC6 *VC6controller = [[VC6 alloc] init];
[urNavController pushViewController:VC6controller];
}
}
You could use UINavigationController's - (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated method to remove any view controllers below the topmost one. Since the navigation controller's viewControllers array is an immutable one, you could not use any NSMutableArray's removeObject... methods directly on the viewControllers array. You would have to make a mutableCopy into a mutable array, remove any (hidden) view controllers you wish to discard from the mutable array, and pass the resulting slimmed-down stack of view controllers to the above method. Since your topmost view controller would be unchanged, there would be no transition animation in your case (see discussion below), so you could also set the viewControllers property directly without bothering with the animated: argument.
From Apple's documentation:
Discussion
You can use this method to update or replace the current view controller stack without pushing or popping each controller explicitly. In addition, this method lets you update the set of controllers without animating the changes, which might be appropriate at launch time when you want to return the navigation controller to a previous state.
If animations are enabled, this method decides which type of transition to perform based on whether the last item in the items array is already in the navigation stack. If the view controller is currently in the stack, but is not the topmost item, this method uses a pop transition; if it is the topmost item, no transition is performed. If the view controller is not on the stack, this method uses a push transition. Only one transition is performed, but when that transition finishes, the entire contents of the stack are replaced with the new view controllers. For example, if controllers A, B, and C are on the stack and you set controllers D, A, and B, this method uses a pop transition and the resulting stack contains the controllers D, A, and B.

Resources