UISplitViewController in portrait on iPhone shows detail VC instead of master UISplitViewController in portrait on iPhone shows detail VC instead of master ios ios

UISplitViewController in portrait on iPhone shows detail VC instead of master


Oh man, this was causing me a headache for a few days and could not figure out how to do this. The worst part was that creating a new Xcode iOS project with the master-detail template worked just fine. Fortunately, in the end, that little fact was how I found the solution.

There are some posts I've found that suggest that the solution is to implement the new primaryViewControllerForCollapsingSplitViewController: method on UISplitViewControllerDelegate. I tried that to no avail. What Apple does in the master-detail template that seems to work is implement the new (take a deep breath to say all of this one) splitViewController:collapseSecondaryViewController:ontoPrimaryViewController: delegate method (again on UISplitViewControllerDelegate). According to the docs, this method:

Asks the delegate to adjust the primary view controller and to incorporate the secondary view controller into the collapsed interface.

Make sure to read up on the discussion part of that method for more specific details.

The way that Apple handles this is:

- (BOOL)splitViewController:(UISplitViewController *)splitViewControllercollapseSecondaryViewController:(UIViewController *)secondaryViewController  ontoPrimaryViewController:(UIViewController *)primaryViewController {    if ([secondaryViewController isKindOfClass:[UINavigationController class]]        && [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[DetailViewController class]]        && ([(DetailViewController *)[(UINavigationController *)secondaryViewController topViewController] detailItem] == nil)) {        // Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.        return YES;    } else {        return NO;    }}

This implementation basically does the following:

  1. If secondaryViewController is what we're expecting (a UINavigationController), and it's showing what we're expecting (a DetailViewController -- your view controller), but has no model (detailItem), then "Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded."
  2. Otherwise, return "NO to let the split view controller try and incorporate the secondary view controller’s content into the collapsed interface"

The results are the following for the iPhone in portrait (either starting in portrait or rotating to portrait -- or more accurately compact size class):

  1. If your view is correct
    • and has a model, show the detail view controller
    • but has no model, show the master view controller
  2. If your view is not correct
    • show the master view controller

Clear as mud.


Here is the accepted answer in Swift. Just create this subclass and assign it to your splitViewController in your storyboard.

//GlobalSplitViewController.swiftimport UIKitclass GlobalSplitViewController: UISplitViewController, UISplitViewControllerDelegate {  override func viewDidLoad() {    super.viewDidLoad()    self.delegate = self  }  func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController: UIViewController!, ontoPrimaryViewController primaryViewController: UIViewController!) -> Bool{    return true  }}


Swift version of Mark S' correct answer

As provided by Apple's Master-Detail template.

func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController:UIViewController, ontoPrimaryViewController primaryViewController:UIViewController) -> Bool {    guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }    guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false }    if topAsDetailController.detailItem == nil {        // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.        return true    }    return false}

Clarification

(What Mark S said was slightly confusing)

This delegate method is called splitViewController: collapseSecondaryViewController: ontoPrimaryViewController:, because that's what it does. When changing to a more compact width size (for example when rotating the phone from landscape to portrait), it needs to collapse the split view controller into only one of them.

This function returns a boolean to decide if it should collapse the Detail and show the Master or not.

So in our case, we'll decided based on if there was a detail selected or not. How do we know if our detail is selected? If we follow Apple's Master-Detail template, the detail view controller should have an optional variable having the detail info, so if it's nil (.None), there's nothing selected yet and we should show the Master so the user can select something.

That's it.