20
votes

In iOS 8, view controllers can now call showDetailViewController:sender: to have the system determine the proper view controller to present the detail view controller.

In my app, I have a UISplitViewController, which contains two UINavigationControllers in its viewControllers array. The first UINavigationController contains my 'master' view, a subclass of UITableViewController. The second UINavigationController contains my 'detail' view.

Since I'm trying to make this work universally, I'm trying to use showDetailViewController:sender: to display the detail view:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{

    self.itemVC.item = self.itemStore.items[indexPath.row];

    [self showDetailViewController:self.itemVC sender:self];
}

This works fine with the Horizontal Compact trait (iPhone style), when self.splitViewController.collapsed == YES, but not when the trait is Regular (iPad, not collapsed). On the iPad, it replaces the detail UINavigationController with the bare detail view controller (instead of replacing that UINavigationController's viewControllers array).

To get around this, I'm tested for whether or not it's collapsed, and if it isn't, I'm wrapping the detail view controller in another UINavigationController before showing it:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{

    self.itemVC.item = self.itemStore.items[indexPath.row];

    UIViewController *vcToShow;

    // For whatever reason, when not collapsed, showDetailViewController replaces the detail view, doesn't push onto it.
    if (self.splitViewController.collapsed) {
        vcToShow = self.itemVC;
    } else {
        vcToShow = [[UINavigationController alloc] initWithRootViewController:self.itemVC];
    }

    [self showDetailViewController:vcToShow sender:self];
}

I suppose alternatively I could just configure self.itemVC and avoid calling showDetailViewController:sender: altogether when self.splitViewController.collapsed == NO:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{

    self.itemVC.item = self.itemStore.items[indexPath.row];

    // For whatever reason, when not collapsed, showDetailViewController replaces the detail view, doesn't push onto it.
    if (self.splitViewController.collapsed) {
        [self showDetailViewController:vcToShow sender:self];
    }
}

But, this feels like it's defeating the purpose of showDetailViewController:sender:, which is to loosen up the coupling between self and the rest of the view hierarchy.

Is there a better way to handle this?

3
Have you made any progress on this? I'm having the same question.pfandrade
Nope - I'm still doing what I ended the post with. But I don't like it.Jeff V
BTW, in the latest beta, you can push a NavigationController even if you're running on the iPhone. You'll only see one navigation bar. They are hiding the innermost UINavigationController's navigationBar.pfandrade
Yeah, I saw that - it worked, but I don't think it popped off the stack properly? I can't remember, but I do remember not feeling any better about that solution.Jeff V

3 Answers

7
votes

In showDetailViewController:sender: depending on the collapse property you need to create the controller you want to show in the detail.

E.g. On the iPad in landscape mode it would already create the detail view controller from the storyboard but on the iPhone 5 where it is collapsed the view controller does not exist yet.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    UINavigationController *detail;
    ImageViewController *imageVC;

   // on the iPhone (compact) the split view controller is collapsed
   // therefore we need to create the navigation controller and its image view controllerfirst
   if (self.splitViewController.collapsed) {
       detail = [[UINavigationController alloc] init];
       imageVC = [self.storyboard instantiateViewControllerWithIdentifier:@"ImageViewController"];
       [detail setViewControllers:@[imageVC] animated: NO];
   }
   // if the split view controller shows the detail view already there is no need to create the controllers
   else {
       id vc = self.splitViewController.viewControllers[1];
       if ([vc isKindOfClass:[UINavigationController class]]) {
           detail = (UINavigationController *)vc;
           imageVC = [detail.viewControllers firstObject];
       }
    }

    [self prepareImageViewController:imageVC forPhoto:self.photos[indexPath.row]];
    // ask the split view controller to show the detail view
    // the controller knows on iPhone and iPad how to show the detail
    [self.splitViewController showDetailViewController:detail sender:self];
}

I hope this solves your issue.

0
votes

The way You doing it have a problem. If your rotate the device(change the mode from collapsed to allVisible) after you select, you will find the detail vc without a navigation controller.

If you call showDetailViewController:sender: in all cases and pass the view controller with a navigation controller it will work fine in both cases and also will fix the rotaion problem mentioned above.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    self.itemVC.item = self.itemStore.items[indexPath.row];

    UIViewController *vcToShow= [[UINavigationController alloc] initWithRootViewController:self.itemVC];
    [self showDetailViewController:vcToShow sender:self];
}
-1
votes
if (self.splitViewController.collapsed)
    [self.splitViewController showDetailViewController:self.itemVC sender:self];
else
    self.splitViewController.preferredDisplayMode = UISplitViewControllerDisplayModePrimaryHidden;