Question

It looks like I found a bug in the navigationController pushViewController method. To recreate it I took the sample master detail project and made some changes to the -didSelectRow: method:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    DetailViewController* view = [self.storyboard instantiateViewControllerWithIdentifier: @"view"];
    NSDate *object = _objects[indexPath.row];
    [view setDetailItem:object];
    [self.detailViewController.navigationController pushViewController:view animated:YES];
}

I also changed the storyboard ID of the detail view to "view". Here's what happens:

When selecting any of master's rows slowly (when the push animation has finished) it works fine: selecting rows slowly

If you select any of the rows while the animation is still showing, the detail view becomes inactive (shows the last row that was called with the push method before the bug) and only the navigation bar shows the push animation of a new view: selecting rows rapidly

When this happens you get some warnings in the debugger output:

Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.

nested push animation can result in corrupted navigation bar

Unbalanced calls to begin/end appearance transitions for <DetailViewController: 0x8a6e070>.

Here's the nasty part: if you go back on the detail view's navigation bar and then select a different row in Master's tableview OR go back twice in the Detail the app crashes with

Can't add self as subview

BUT if you run the same code on ios6 it works without any problems or warnings.

Current solutions:

Calling the method with animated:NO seems to work without any problems, obviously there's no push animation.

Another solution is to have a boolean value in Master set it to false when calling the push method and setting it to true from Detail in Detail's viewDidAppear. That way you can check the value when calling the push method. Drawbacks: the tableView might appear laggy if you try to quickly select the same row, if you select a different row quickly then it wont show up in Detail but it will get selected in the Master tableview and get highlighted, you can save the selected row's index and change the selection in code but that leads to flickering row highlighting.

Both solutions involve some compromise, so I'd like to find another way of handling this. Are there any better solutions?

Was it helpful?

Solution

After some testing it appears that in ios6 when a tableview cell gets selected and the push gets called the tableview sets .allowsSelection to NO until the push ends.

This can be easily reproduced in ios7 by setting .allowsSelection to NO after the push method in didSelectRowAtIndexPath: and having a callback in the didAppear: method of the detail's view where we set the tableview's .allowsSelection to YES in the master view.

While this works perfectly fine I found it a bit annoying when quickly selecting two different cells one after the other that the second one will get ignored.

So i've set up that if another cell is being selected while the push animation hasn't ended I save a reference to the new view and push it as soon as the first animation has ended. By doing this the tableView seems a bit more responsive in my opinion.

OTHER TIPS

The reason behind your is pushing a view controller while the other hasn't been fully pushed (logs already warns you from doing so).

The push animation is supposed to take approx 0.4 sec, so the user has to be fast enough to tap another row within 0.4 sec, yes that is applicable, but isn't a normal behavior.

what you can do is to edit the animation time to be less than 0.4 sec (may be 0.1 or 0.2) using the code below in your -didSelectRow: method:

DetailViewController* view = [self.storyboard instantiateViewControllerWithIdentifier: @"view"];
NSDate *object = _objects[indexPath.row];
[view setDetailItem:object];

[UIView  beginAnimations:@"ShowDetails" context: nil];
[UIView setAnimationCurve: UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.2];
[self.detailViewController.navigationController pushViewController:view animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.navigationController.view cache:NO];
[UIView commitAnimations];

Hope it helps :)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top