9
votes

I've tried to expand the default Apple MasterDetail Template by adding a UITabbarController in front of the UINavigationController of the MasterView, so there is a structure like this:

UISplitViewController (Master) > UITabbarController > UINavigationController > UITableViewController

But if I run the App, after changing application(didFinishLaunchingWithOptions) to use the correct ViewController, and try to perform the ShowDetails Segue the DetailsView ist presented Modally on the iPhone. On the other side the iPad Version is working as expected. What am I forgot to do? Or how can I fix it?

6

6 Answers

7
votes

Just to update the answers above. Since you can't push navigation controllers anymore, you have to push its top view controller instead.

    func splitViewController(splitViewController: UISplitViewController, showDetailViewController vc: UIViewController, sender: AnyObject?) -> Bool {
        if splitViewController.collapsed {
            let tabBarController = splitViewController.viewControllers.first as! UITabBarController
            let selectedNavigationViewController = tabBarController.selectedViewController as! UINavigationController

            // Push view controller
            var viewControllerToPush = vc
            if let navController = vc as? UINavigationController {
                viewControllerToPush = navController.topViewController
            }
            selectedNavigationViewController.pushViewController(viewControllerToPush, animated: true)

            return true
        }

        return false
    }
8
votes

I figured out how to put the detail on to the master's UINavigationController instead of presenting it modally over the UITabBarController.

Using the UISplitViewControllerDelegate method

- splitViewController:showDetailViewController:sender:

In case the UISplitViewController is collapsed get the masters navigation controller and push the detail view onto this navigation controller:

- (BOOL)splitViewController:(UISplitViewController *)splitViewController
   showDetailViewController:(UIViewController *)vc
                     sender:(id)sender {
    NSLog(@"UISplitViewController collapsed: %d", splitViewController.collapsed);

    // TODO: add introspection
    if (splitViewController.collapsed) {
        UITabBarController *master = (UITabBarController *) splitViewController.viewControllers[0];
        UINavigationController *masterNavigationController = (UINavigationController *)master.selectedViewController;

        // push detail view on the navigation controller
        [masterNavigationController pushViewController:vc animated:YES];

        return YES;
    }

    return NO;
}
1
votes

Here's my solution. Place in MasterViewController.m and remember to give your detail view a Storyboard ID in IB. In my case 'detail'.

-(BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
    if ([identifier isEqualToString:@"showDetail"] && self.splitViewController.collapsed) {
        DetailViewController *myController = (DetailViewController *)[self.storyboard instantiateViewControllerWithIdentifier:@"detail"];
        NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
        NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
        [myController setDetailItem:object];
        [self.navigationController showViewController:myController sender:self];
         return NO;
    }
    return YES;
}
1
votes

There is another way to do it without code.

After you embedded the the UINavigationController in the TabBarController embed the TabBarController in another UINavigationController. So you will have: SplitViewController -> Master -> NavCon -> TabBar -> NavCon -> TableViewController.

It's much easier doing like this, but there a bug that I haven't found out how to fix. The navigation bar presented will be that of the TabBarController, not the TableViewController. Any ideas how to fix that?

1
votes

Subclass TabBarController like this:

- (void)showViewController:(UIViewController *)vc sender:(id)sender
{
    if ([self.selectedViewController isKindOfClass:UINavigationController.class])
        [self.selectedViewController showViewController:vc sender:sender];
    else
        [super showViewController:vc sender:sender];
}

- (UIViewController*)separateSecondaryViewControllerForSplitViewController:(UISplitViewController *)splitViewController
{
    return [self.selectedViewController separateSecondaryViewControllerForSplitViewController:splitViewController];
}

- (void)collapseSecondaryViewController:(UIViewController *)secondaryViewController forSplitViewController:(UISplitViewController *)splitViewController
{
    [self.selectedViewController.navigationController collapseSecondaryViewController:secondaryViewController forSplitViewController:splitViewController];
}

See this question for complete explanation.

0
votes

Here is an alternative that is based on testing the size classes of the splitViewController :

  1. Use a custom UISplitViewController (subclass)
  2. Override the showDetailViewController operation
  3. Use the traitCollection to determine the class of the UISplitViewController
  4. If the horizontal class is Compact, get the navigationController to call showViewController

Here is the the code of the custom UISplitViewController :

import UIKit

class CustomSplitViewController: UISplitViewController {

    override func showDetailViewController(vc: UIViewController!, sender: AnyObject!) {

        if (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClass.Compact) {
            if let tabBarController = self.viewControllers[0] as? UITabBarController {
                if let navigationController = tabBarController.selectedViewController as? UINavigationController {
                    navigationController.showViewController(vc, sender: sender)
                    return
                }
            }
        }

        super.showDetailViewController(vc, sender: sender)
    }
}

Do not forget to the set the custom class in the storyboard.

Tested in the simulator of iPhone 6, iPhone 6+ and iPad Air and worked as expected.