How can I pop a view from a UINavigationController and replace it with another in one operation?
I have an application where I need to remove one view from the stack of a UINavigationController and replace it with another. The situation is that the first view creates an editable item and then replaces itself with an editor for the item. When I do the obvious solution within the first view:
MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];
[self retain];
[self.navigationController popViewControllerAnimated: NO];
[self.navigationController pushViewController: mevc animated: YES];
[self release];
I get very strange behavior. Usually the editor view will appear, but if I try to use the back button on the nav bar I get extra screens, some blank, and some just screwed up. The title becomes random too. It is like the nav stack is completely hosed.
What would be a better approach to this problem?
Thanks, Matt
I've discovered you don't need to manually mess with the viewControllers
property at all. Basically there are 2 tricky things about this.
-
self.navigationController
will returnnil
ifself
is not currently on the navigation controller's stack. So save it to a local variable before you lose access to it. - You must
retain
(and properlyrelease
)self
or the object who owns the method you are in will be deallocated, causing strangeness.
Once you do that prep, then just pop and push as normal. This code will instantly replace the top controller with another.
// locally store the navigation controller since
// self.navigationController will be nil once we are popped
UINavigationController *navController = self.navigationController;
// retain ourselves so that the controller will still exist once it's popped off
[[self retain] autorelease];
// Pop this controller and replace with another
[navController popViewControllerAnimated:NO];
[navController pushViewController:someViewController animated:NO];
In that last line if you change the animated
to YES
, then the new screen will actually animate in and the controller you just popped will animate out. Looks pretty nice!
The following approach seems nicer to me, and also works well with ARC:
UIViewController *newVC = [[UIViewController alloc] init];
// Replace the current view controller
NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:[[self navigationController] viewControllers]];
[viewControllers removeLastObject];
[viewControllers addObject:newVC];
[[self navigationController] setViewControllers:viewControllers animated:YES];
From experience, you're going to have to fiddle with the UINavigationController's viewControllers
property directly. Something like this should work:
MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];
[[self retain] autorelease];
NSMutableArray *controllers = [[self.navigationController.viewControllers mutableCopy] autorelease];
[controllers removeLastObject];
self.navigationController.viewControllers = controllers;
[self.navigationController pushViewController:mevc animated: YES];
Note: I changed the retain/release to a retain/autorelease as that's just generally more robust - if an exception occurs between the retain/release you'll leak self, but autorelease takes care of that.