Manipulating view hierarchy by code - ios

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

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.

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

Skipping view controllers in a UINavigationController stack

Is it bad practice to skip view controllers in a navigation stack?
In my example I have a UINavigationController (which is being presented modally) with three UIViewControllers (firstViewController, secondViewController and thirdViewController), most of the time the user will be accessing firstViewController, maybe tapping a row which will then load secondViewController and so on, but what if in some circumstances I want to load the second or third view controllers immediately but still keep the first view controller in the stack, so that the user can still go back to the firstViewController.
I can do this but it doesn't feel right - is this something I should avoid doing?
Yes, it's called deep-linking, and it's perfectly fine. It's quite commonly done for things like coming from an external URL or push notification.

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

Modifying UINavigationController stack with modal view controllers

I have the following environment:
root view controller is UINavigationController
a number of custom UIViewControllers may be pushed on the navigation stack
each of custom controllers may or may not present a modal view controller
I need to be able to programmatically manage the navigation stack (for example - drop all controllers from the navigation stack except the root controller as response to some external event like push notification delivery)
Naive implementation with [navigationController setViewControllers:newControllers animated:animated]; obviously fails if there was a modal view controller presented by any of old controllers ind the stack:
This modal controller stays visible
If a delegation pattern is used for parent<->modal controllers communication (parent is delegate of presented modal view controller) any action in modal view controller results in crash since delegate was already released
So the general problem is that modal controler lifcycle is not bound to the parent controller. My questions are:
Is there a stadard approach for managing this kind of hierarchy and safe navigation stack changes?
If NO than what would be a best custom implementation? I'm seeing two general approaches - one is to explicitly dismiss/unlink all modal contrellers in the code changing the navigation stack, the other is to add logic to parent view contollers to manage modal controllers lifecycle directly.
There is no standard approach because this behaviour is discouraged by the HIG. Even in the event of push notifications, you're not supposed to modify the existing stack except through pushes, pops, and pop-to-root. However, what you're asking is completely possible.
You have several options, but the best is probably notifications. Use NSNotificationCenter in your app delegate to let any interested view controller's know that you're about to pop to your root view controller. In each of your modal view controllers, register for this notification name and dismiss yourself when notified. After dismissing your modals, you can just use popToRootViewController and avoid messiness with modifying the stack.
Take a look at TweetBot and see how they handle push notifications. They do a pretty good job, I believe, and they just present a new modal view controller. You can do this (nested modal presentations), so experiment around and see what you can do without jarring the user.
Check this method:
http://developer.apple.com/library/ios/documentation/uikit/reference/UINavigationController_Class/Reference/Reference.html#//apple_ref/occ/instm/UINavigationController/popToRootViewControllerAnimated:
Also, modal controllers are modal for a good reason - to draw user’s attention on one task or unit of work or whatever your app does. So, if you want to programatically hide it and return to some other controller in navigation stack, it’s kind of obvious you need to deal with the modal controller first.
call [self dismissModalViewControllerAnimated:YES]; from -(void)viewDidUnload method of the viewcontroller.

Resources