Sunday, June 9, 2013

UINavigationController setViewControllers animated example in Objective C (iOS).

UINavigationController setViewControllers animated

Replaces the view controllers currently managed by the navigation controller with the specified items.

- (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated

Parameters of [UINavigationController setViewControllers animated]
The view controllers to place in the stack. The front-to-back order of the controllers in this array represents the new bottom-to-top order of the controllers in the navigation stack. Thus, the last item added to the array becomes the top item of the navigation stack.
If YES, animate the pushing or popping of the top view controller. If NO, replace the view controllers without any animations.

Discussion of [UINavigationController setViewControllers animated]
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.[UINavigationController setViewControllers animated]

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.

UINavigationController setViewControllers animated example.
Finally I found the solution. The mistake was that the new top-level NavigationController has not been initialized and loaded properly until the animation is done. In my case, UserAssignmentsListViewController has a viewDidLoad method that will not be called until animation is done, but it sets the navigation title (here: a UIButton). Therefore the animation fails.

The solution is to refer to an already initialized view controller when it comes to pushing it to the stack. So initialize our top-level VC somewhere:

// initialize our top-level controller
ViewController* viewController2 = [[[ViewController alloc]
    initWithNibName:@"ViewController" bundle:nil] autorelease];
Then when pushing two or more VCs to the stack, the top level one is already initialized and the animation works (following the example from my original question):

NSMutableArray* viewControllers = [NSMutableArray arrayWithCapacity:2];
// put us on the stack, too
[viewControllers addObject:self];

ViewController* viewController1 = [[[ViewController alloc]
    initWithNibName:@"ViewController" bundle:nil] autorelease];
[viewControllers addObject:viewController1];

if (someCondition == YES)
    [viewControllers addObject:viewController2];

    setViewControllers:[NSArray arrayWithArray:viewControllers] animated:YES];

Example of [UINavigationController setViewControllers animated].
//Create view controller which confirms backup/restore complete
ConfirmationViewController *v = [[ConfirmationViewController alloc] init];

//Create array containing only root view controller and confirmation view controller
NSArray *viewControllers = [[NSArray alloc] initWithObjects:[self.navigationController.viewControllers objectAtIndex:0], v, nil];
[v release];

//Push the confirmation view controller so the user can see it
[self.navigationController pushViewController:v animated:YES];

//Set navigationController's viewControllers array in order to remove the intermediary view controllers. Back button now goes back to root viewController
[self.navigationController setViewControllers:viewControllers animated:YES];
[viewControllers release];

UINavigationController setViewControllers animated example.
[self performSelector: @selector(launchPhotoViewer)
                                   withObject: nil
                                   afterDelay: 0.1];


PhotoTest1Controller* test = [[PhotoTest1Controller alloc] init];
        [navigationController pushViewController:test animated:NO];

        // Remove previous view the hard way
        NSArray* orig = [navigationController viewControllers];
        NSMutableArray* newa = [[NSMutableArray alloc] init];
        for (id i in orig)
                [newa addObject:i];
        [newa removeObjectAtIndex:[orig count]-2];
        [navigationController setViewControllers:(NSArray*)newa]; 

End of UINavigationController setViewControllers animated example article.