I’m designing the interface for an iOS app in which there are both hierarchical navigation modes and arbitrary view-swapping modes. The layout of any given view in one mode is distinct from that of the corresponding view in the other mode.
I want the user to be able to switch back and forth between these modes. Say an isolated subview has a corresponding detail view that is 2 levels down in the UINavigationController’s hierarchy. The user should be able to switch directly to that navigation detail view. From there, he should be able either to navigate up and down the UINavigationController hierarchy or to switch back to the isolated subview from which he came.
UINavigationController has a method popToViewController:animated: that will let you skip levels as you navigate back up the hierarchy, but it has no corresponding pushToViewController:animated: that would let you jump directly to a lower level in the hierarchy. That makes sense — the nav controller must push the intervening view/s onto the stack before it displays the target view. But the regular push methods display the intervening view/s, which I assume will cause a visual flash as well as exacting a performance hit.
If I simulate pushToViewController by calling pushViewController twice consecutively, with animated:NO for the intervening view and with the intervening view set temporarily to be transparent, will I get reasonable performance? Or, when the nav mode is requested, should I place the nav controller's stack behind the isolated views, do the multiple calls to pushViewController, and then move the nav controller to the front? Or should I forgo the use of UINavigationController altogether and do all the table-view-style navigation manually via UIView’s add/remove/insert/exchangeSubview?
I believe setViewControllers:animated: is going to help you do what you want.
When you want to add several view controllers to the stack and directly go to the last one, you can explicitly set the entire stack and have the UINavigationController animate navigation directly to the last one.
NSMutableArray* controllers = [NSMutableArray arrayWithArray:[self.navigationController viewControllers]];
[controllers addObject:newController1];
[controllers addObject:newController2];
[self.navigationController setViewControllers:controllers animated:YES];
This will push newController1 and newController2 to your view stack and animate a transition to newController2.
Related
I'm working on a app which uses the controller containment pattern as described in the View Controller documentation in the iOS SDK.
I've written the controller container, and it works great. My controller is basically containing two sub views and it displays them both at the same time, sliding one over the other depending on what the user is doing. Works wonderful.
Now, I want to use this container controller in a navigation view. This is to get push segues to work. In effect, from my contained controllers, I want to be able to use the navigation stack, push a new controller on, and pop when the user is done.
However, I have noticed that if the navigation view is instantiated with my container controller as the root container, things fall apart.
In particular, I have noticed this:
In the iOS documentation, container controllers call addChildController: and then addSubview:. This seems to break the navigation stack, as the push segue does not work - it behaves like modal. I believe it does this because addSubview resets the navigation stack.
I confirmed this by replacing addChildController and addSubview with [self.navigationController pushViewController...]. I confirmed it is a problem with addSubview because I can reproduce the issue when I omit the call to addChildController.
When I do this, the navigation stack works properly. But of course, my container controller does not, as only the "most recently pushed" controller is visible.
I'm doing this because in my contained controllers, I want to push a new controller onto the stack, and when the user is done, I want to "pop" the stack, without reloading the "previous controller".
Using a modal segue reloads the previous controller; using a push controller does not.
I cannot find any documentation on the behavior of addSubview and it's effect on the navigation stack.
Thank you in advance for any light you guys can shed!
I'm having a bit of trouble completely understanding what you are doing, but I think that what you want to do is exactly what I'm doing.
I have a UINavigationController that has as its rootView a container UIViewController. That controller adds children per the normal methods. One of those children views pushes other views that may get popped.
One of those pushed views COULD message the appDelegate and make itself the rootViewController if it wanted to. In general, as long as you keep a strong reference to a view controller, you can remove it from whoever 'owns' it, and muck around with the navigationControllers viewControllers array to your hearts content.
I have a UINavigationController within one of the tabs of a UITabBarController.
I now present a new view controller (let’s call it Steve) over the whole app (using presentViewController:animated:completion:).
Then, I simulate low memory.
After dismissing Steve (using dismissViewControllerAnimated:completion:), I can now see that the UINavigationController’s view is gone; within the tab; only an empty white area is seen!
Why is this? I have tried calling the view methods on all imaginable controllers upon Steve’s dismissal, but the contents of the tab still stay empty (white).
The strange thing is this: If I click on another tab, and click back on the original tab, the contents (the navigation controller) shows just fine again. Is the tab bar controller doing something special to force the view to display?
UPDATE: I was able to “fix” my issue with this terrible code, just before dismissing Steve:
[[[[[self tabBarController] view] subviews] objectAtIndex:0]
addSubview:[[self navigationController] view]];
What this does is that it finds the subview of the tab bar controller which is not the tab bar (i.e. the top view), and then adds the navigation controller’s view to be its subview.
This is of course terrible, because it makes internal assumptions about the subview structure of the tab bar controller’s view.
If someone has any better solutions, please let me know about them.
When your app receives a memory warning, one of the first thing it will do is drop the view hierarchies of any view controllers that have loaded views but are not currently visible (like your UINavigationController). Most likely, whatever view controller is at the top of your navigation stack is having its views dropped but not reloading them when it reappears.
Always put your view construction code in -loadView or -viewDidLoad and not in -init. That way, the view controller will reconstruct your view if it's been dropped due to a memory warning.
(P.S.: The reason your hack works is the bit where you call [[self navigationController] view], which in turn calls -loadView on the top VC in the stack, forcing it to reconstruct its view.)
I have a UINavigationController, complete with table view and associated magic.
The data I'm populating that table view from may have items from multiple categories, but the default view for the user will be one in which they are viewing all of the items, and then they have the ability to move backwards to a different table view that would allow them to select a different category, which would then return to the original table view with the appropriate data populated.
What's the proper approach for this? I can't seem to wrap my head around how I would make the navigation controller give me a back button (with appropriately wired up actions) without having come from a previous view in the stack (which wouldn't really exist at launch time if I start the user off from what is essentially the detail view, in stack terms.)
Also, the back button should be titled "Groups", not "Back", but that's really just an implementation detail. :)
Update: This issue finally manifested itself in production code, and here’s how I fixed it:
My UINavigationController is created in a nib, with the root view set as the “groups” view. Then, in my app delegate, I push the second view onto the stack while the app is launching.
That works fine for achieving the proper stack, but that doesn’t help with the back button title, because the navigation controller didn’t seem to want to grab the title from the root view, and instead was showing a back button with “Item” as the title.
So, on the pushed view, in viewDidLoad, I set:
self.navigationController.navigationBar.backItem.title = #"Groups";
and that did the trick.
The only potential downside of doing it this way would be if the pushed view controller were ever used in a scenario where the view below it wasn’t the groups view, but since the design of this particular application ensures that never happens, I’m accepting that failure. ;)
Another update:
I’m an idiot. Just set the title property of the navigationItem provided by the navigationController in Interface Builder, and boom, no issue. Or do it in code. It doesn’t matter, just don’t do it by setting the backItem.title way I show you above. That’s just dumb.
In your application delegate's .m file in the application:didFinishLaunchingWithOptions: method just push your view controllers like you normally would with[self.navigationController pushViewController:your_view_controller animated:YES]; and it should push them on before the application's first view controller appears.
To change the text of the button to Groups just call this before pushing your controllers.:
UIBarButtonItem *newBackButton = [[UIBarButtonItem alloc] initWithTitle: #"Groups" style: UIBarButtonItemStyleBordered target: nil action: nil];
[[self navigationItem] setBackBarButtonItem: newBackButton];
[newBackButton release];
Why do Navigation Applications use pushViewController instead of presentModalViewController like all the other apps? How can a Navigation Application be modified to use presentModalViewController instead? Would it be sub-optimal to do so? Why?
Navigation view controllers and modal view controllers are there for different purposes. The first is used for display hierarchical nested contents. While you request more detailed info about an item, you go deeper in the hierarch pushing more detailed views over the stack.
The modal view is there for displaing only one view over the current. Its usefull for stuff like an info button for your app.
Your question is a little bit like asking why UISplitViewControllers use two controllers and lay their views out side-by-side. That is, UINavigationControllers use pushViewController: to manage their stack of UIViewController instances because that's how Apple decided UINavigationControllers should work. When animated into view, pushed views will slide in from the right and old views slide in from the left when a view is popped.
ANY instance of a UIViewController can use presentModalViewController to display the view of another UIViewController over top of it's own view in a manner which prevents the user from interacting with the view underneath. Depending on the device (iPhone, iPad) you have various options for the visual appearance of the newly presented view and the animation used to bring it into view.
There's nothing stopping you from writing an application that just keeps having one view bring up the next view using presentModalViewController but there'd be no reason to use a UINavigationController to do so. I've never checked if there was a meaningful difference in memory consumption or any other thing you could measure to judge whether doing so is "sub-optimal" from a technical perspective, but it's certainly not the norm so might be sub-optimal from the user experience perspective. Whether that is true or not for your app depends on whether users seem to think the interaction makes sense to them.
Navigation view controllers uses the concept of Stack.Your navigation is stored in stack that's why you can push & pop the View which shows that you can use them for the detail view ....making a hierarchy of views
whereas modal view controllers shows only one view at a time......this is generally used for new flow in app.
Can anyone suggest an article or perhaps an example of how to create an "infinite drill-down" with UINavigationController like you see in the Facebook, IMDB and BrightKite apps?
The UINavigationController is designed to be used this way. If memory becomes an issue, the nav controller will release hidden views and reload them when the time comes to navigate back down the stack. Apple recommends you load your UIViews from a NIB for this reason.
http://developer.apple.com/iphone/library/featuredarticles/ViewControllerPGforiPhoneOS/NavigationControllers/NavigationControllers.html#//apple_ref/doc/uid/TP40007457-CH103-SW28
Scroll down to item #4, "Configure the view for your root view controller."
Never having done this... I would approach this by keeping a stack with a light-weight object containing the contents of the previous view (perhaps just a URL?). Rather than just push on a new UIViewController to the UINavigationController, you would pop your current one, without animation, then push on the new view. However, I don't think this would properly handle the nice animation.
Another way that comes to mind would be to manipulation the viewControllers array of UINavigationController. After pushing on a new view, just remove the previous view from the array. That way the UINavigationController stack would only ever be 1 or two elements deep. Handling the back button would create the proper view, insert it into the viewControllers array, then pop off the current one. Your state would be managed by the lightweight object stack, not heavyweight view controllers.
-dan