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.
Related
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.
I have an WKInterfaceController containing a WKInterfaceTable which calls the
- (void)presentControllerWithNames:(NSArray *)names contexts:(NSArray *)contexts
to present 2 paged controllers modally. If I call
- (void)presentControllerWithName:(NSString *)name context:(id)context
from one of those controllers, or more specifically
- (void)presentTextInputControllerWithSuggestions:(NSArray *)suggestions
allowedInputMode:(WKTextInputMode)inputMode
completion:(void (^)(NSArray* results))completion
and then dismiss the newly presented controller, I loose the Cancel button on the presenting modal controller. So I have no way of getting back to the root controller.
Your question is a bit difficult to understand, but I believe the root cause of the issue you are having is that you are presenting a modal interface controller on top of another modal interface controller which is not what you want to do. You will run into issues like you are seeing such as Cancel buttons not appearing.
Instead, I would encourage you to think outside the box. Instead of presenting a modal on top of another modal, what if you switched page sets using the WKInterfaceController reloadRootControllersWithNames(_:contexts:). Then you would no longer have your modal on top of a modal problem.
In summary, navigation design in Watch Apps is a bit tricky, but can generally work out well if you follow the common rules.
Only use a hierarchical or page based layout system
Don't present modals on top of modals
Hopefully this helps shed some light.
I have an app which uses UILocalNotifications (though the question would also be relevant to PushNotifications). When the user receives a local notification, depending on the state of the app and the choice they make in the notification alert, I want to present a specific viewController. The issue is they may currently be in a "pushed" viewController through a segue or in a modal ViewController and I am not currently tracking where the user is within the possible branches.
I am wondering if there is any sort of design pattern or standard way of handling a mixed presentation mode app like this? Particularly if there is any built-in way to determine which is/are the active ViewController(s) without my having to setup a breadcrumb system. This would matter, for example, if the user is already viewing the viewController to be called as a result of the localNotification.
I realize my need/explanation is a little muddled. I'll edit and update this if anyone needs more info.
OK - I think I have a solution through the Apple docs View Controller Programming Guide for iOS: Presenting View Controllers from Other View Controllers
Each view controller in a chain of presented view controllers has
pointers to the other objects surrounding it in the chain. In other
words, a presented view controller that presents another view
controller has valid objects in both its presentingViewController and
presentingViewController properties. You can use these relationships
to trace through the chain of view controllers as needed. For example,
if the user cancels the current operation, you can remove all objects
in the chain by dismissing the first presented view controller. In
other words, dismissing a view controller dismisses not only that view
controller but also any view controllers it presented.
So I can cancel any chain of modal viewControllers by dismissing the first modal viewController in the chain.
I can determine if there is currently a modal viewController being presented using:
self.window.rootViewController.presentedViewController
If the property is null then there no modal viewController is currently being presented. And for completeness (hackishness..) I can test is the presentedViewController has a presentedViewController, etc.:
self.window.rootViewController.presentedViewController.presentedViewController
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.
What is the difference beetween calling presentModalViewController and pushViewController, when :
animation is set to NO (even if yes, that's just an animation style that can be changed).
a navigation controller is defined when presenting the modal view, so it can be navigable too, with a call stack, ....
Is this just to be able to go back from the first pushed view ? Woooaaaaaa.....
I guess the difference is elsewhere and deeper. No ?
Ignoring transitions/animations and how things are structured behind the scenes (which aleph_null's alswer provides a good discussion of), the only user-facing difference is the ability to return to the preceding view automatically using the navigation bar.
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.
The most important difference is about semantics. Modal view controllers typically indicate that the user has to provide some information or do something. This link explains it more in depth: http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ModalViewControllers/ModalViewControllers.html
Here's another, less abstract difference they talk about:
"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."
Also see this thread: why "present modal view controller"?
Take a look into the viewControllers in the image
The top 2 viewControllers(login & submit) at the top left are disconnected from the tabBarController & NavigationController
The rest of the viewControllers are embedded in a NavigationController. They somehow belong to the natural flow of the app.
Now you have to ask yourself
Do I need to always show login + submit page every time? It would be pain in the neck for the user to each time go to login even if they logged in last time. These 2 screen really don't fit the natural flow of the screens. So what do we do? We just add them modally using presentViewController
However for the rest of the viewControllers we want to keep them inside 2 navigation so we can easily go back and forth so we use pushViewController
For more information I recommend you to see this video
The image was also picked from this great answer. It's worthy of a look.
This is what my experience says,if you want to manage a hierarchy of views,better go for pushViewController in the navigation controller. It works like a stack of view-controllers in the navigation controller. If however the requirement is just to show a view on executing some actions on the parent view controller then the best way is presenting it modally.
If you need a complex push pop logic always prefer a pushViewController.
UINavigationController are used when you want to have some sort of hierarchal representation of your data (ie drill down). They work using a stack of UIViewController subclasses. Every time you “drill down”, you simply add another view controller to the stack. Then, the “back” logic is simply a matter of popping view controllers off of a stack.
You can check out this link:
http://www.icodeblog.com/2011/10/11/back-to-basics-an-introduction-to-view-controllers/