Ios – UISplitViewController – multiple detail views with UINavigationController

iosuinavigationcontrolleruisplitviewcontroller

I'm converting an iPhone app to be a universal app, and it's been mostly straight forward to convert the nested tables into a UISplitViewController arrangement, but I have a remaining issue when running on an iPad that is giving me a headache.

For universal app compatibility, the 'master' view contains a UINavigationController that is used to navigate through a series of TableViews that each displays a menu. This works fine.

Eventually, the user arrives at content that is displayed in the detail view. Each detail view 'chain' is contained in a UINavigationController, as some views can drill down to show maps etc. The idea is that the popover button will live at the root level of the detail view. It's probably important to note that the detail views are created from scratch every time that row is selected.

I've studied Apple's Multiple Detail View Sample Code , and so use the master view as the UISplitViewController delegate, which provides the hide/show popover selectors, and then passes the calls down to whichever substitute detail view is selected.

When working in landscape mode, I can select different rows in the master view, and the detail views switch nicely – everything works great. It's wonderful.

In portrait mode, things don't work quite so well… the popover button displays correctly in the currently selected detail view when rotating to portrait, but then disappears when a row is selected (i.e. it's somehow not being added correctly to the newly selected view's NavBar).

I've added diagnostic code, and it looks like the correct calls (with correct pointers) are being made to show the popover button on the newly selected detail view. Also, I can rotate to landscape and back again, and the popover button then appears so I'm reasonably satisfied that the popover UIBarButtonItem is being hooked up to the new detail NavBar correctly.

As the detail views are not created until the row is selected, I was wondering if this was a case of the UINavigationBar not being instantiated at the time that showRootPopoverButtonItem is called (based on Apple's sample code). This theory is supported by the fact that the popover button appears if I rotate to landscape and back again (as mentioned above) with the same view selected.

I also see this comment in Apple's sample code, in didSelectRowAtIndexPath, and just before switching the detail views, note the use of the word 'after'…

// Configure the new view controller's popover button (after the view has been displayed and its toolbar/navigation bar has been created).

So, I tried calling the showRootPopoverButton method again in viewWillAppear (by which time the UINavigationBar should exist), but that doesn't cause the popover button to appear either.

I'd appreciate any thoughts and suggestions as to how to get the popover button to appear immediately when a new row is selected from the master view when in portrait mode. Thanks.

Thanks for reading this far, the relevant code is below.

From the master view, here are the UISplitViewControllerDelegate selectors,

- (void)splitViewController:(UISplitViewController*)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem*)barButtonItem forPopoverController:(UIPopoverController*)pc
{

    // Keep references to the popover controller and the popover button, and tell the detail view controller to show the button.
    barButtonItem.title = @"Root View Controller";
    self.popoverController = pc;
    self.rootPopoverButtonItem = barButtonItem;
    //UIViewController <SubstitutableDetailViewController> *detailViewController = [self.splitViewController.viewControllers objectAtIndex:1];

    // ^ Apple's example, commented out, my equivalent code to obtain
    // active detail navigation controller below,

    UINavigationController *detailNavController = [self.splitViewController.viewControllers objectAtIndex:1];
    UIViewController *detailViewController = detailNavController.visibleViewController;

    [detailViewController showRootPopoverButtonItem:rootPopoverButtonItem];
}


- (void)splitViewController:(UISplitViewController*)svc willShowViewController:(UIViewController *)aViewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
     // Nil out references to the popover controller and the popover button, and tell the detail view controller to hide the button.
     UINavigationController *detailNavController = [self.splitViewController.viewControllers objectAtIndex:1];
     UIViewController *detailViewController = detailNavController.visibleViewController;
     [detailViewController invalidateRootPopoverButtonItem:rootPopoverButtonItem];
     self.popoverController = nil;
     self.rootPopoverButtonItem = nil;
}

And, very much like Apple's example, here's what happens when a row is selected in the master table,

if (rootPopoverButtonItem != nil)
{
    NSLog (@"show popover button");
    [newDetailViewController showRootPopoverButtonItem:self.rootPopoverButtonItem];
}

From the detail view,

- (void)showRootPopoverButtonItem:(UIBarButtonItem *)barButtonItem
{
    NSLog (@"detailViewController (view: %p, button: %p, nav: %p): showRootPopoverButton", self, barButtonItem, self.navigationItem);

    barButtonItem.title = self.navigationItem.title;

    [self.navigationItem setLeftBarButtonItem:barButtonItem animated:NO];

    popoverButton = barButtonItem;
}


- (void)invalidateRootPopoverButtonItem:(UIBarButtonItem *)barButtonItem
{    
    NSLog (@"detailViewController (%p): invalidateRootPopoverButton", self);

    // Called when the view is shown again in the split view, invalidating the button and popover controller.
    [self.navigationItem setLeftBarButtonItem:nil animated:NO];

    popoverButton = nil;
}

Best Answer

There are two things that I think could be the problem here. You should include the rest of your code. Specifically the part where you change the detail view controller when the user performs an action in the master.

  1. visibleViewController may be nil if you just instantiated detailNavController. Even if you set it's root, there is no "visible" view controller since it actually hasn't displayed yet. You may want to try using topViewController

  2. I'm not sure if you're creating a new detailNavController every time the user selects something in the master but if you are, you need to pass the rootPopoverButtonItem into the detailViewController again because - (void)splitViewController: willHideViewController: withBarButtonItem: forPopoverController: only gets called automatically when the orientation changes.

Related Topic