Split View Controller Display Modes

The split view controller is a complicated controller to master with many configuration properties and delegate methods with long complicated names. In this post I take a look at a minimal Xcode project to create my preferred setup for an adaptable master-detail layout that keeps both view controllers visible when screen size allows.

Split Views - A Recap

I am going to assume you are familiar with the basic setup of a split view controller. It is a container for a master-detail style interface. Actions or choices made by the user in the primary (master) view controller change what the secondary (detail) view controller displays.

In the example below I have a master view controller with a single button though it might typically be a table view. The button action updates a timestamp in the detail view controller displayed side by side with the master view.

Master-detail split view

The display mode button allows the user to switch between having both views visible and having the primary view hidden as below:

Primary hidden

The split view controller started as an iPad only controller but in iOS 8 gained adaptability to make it available on all iOS devices. When presented in a compact width the split view controller collapses the two views into the familiar navigation view hierarchy.

Collapsed

With horizontally regular size classes such as the iPad you can choose between display modes to show both view controllers side-by-side or have the master view controller hidden or overlaying the detail view controller. These modes also collapse down gracefully when the user switches to a slide over or split screen mode.

In this post I am going to concentrate on the display mode that keeps both views visible when possible. I much prefer this to the default which hides the master view but it is easy to adapt to the other modes if required.

Preferred Display Mode

The preferredDisplayMode property is what controls the arrangement of the split view interface when it is not collapsed. It has four possible values (Objective-C values shown in parentheses):

For the iPad the automatic mode switches between allVisible in landscape and primaryOverlay in portrait. For the larger iPhone 6 and 7 Plus devices it uses allVisible in landscape. For other devices and orientations the split view controller collapses the interface.

Basic Setup for All Visible Split View

I don’t like the Xcode master-detail template for creating a split view controller. It uses the app delegate as the split view controller delegate and I much prefer to use the primary or master view controller for that. I also find the allVisible display mode to be a better starting point most of the time. So let’s look at the steps to create a basic setup that prefers to show both view controllers side-by-side when possible.

Creating the Split View Controller

You can easily create the view controllers in code but for brevity I will show it in a Storyboard:

Split View Controller Storyboard

The master and detail view controllers are both embedded in navigation controllers. To keep it simple I have a single button in the master view controller that fires a show detail segue to the detail navigation controller to set the timestamp.

Configuring the Split View Controller

Even if you are creating the split view controller in a Storyboard you still need some code for configuration you cannot do in Interface Builder.

We need to set the preferred display mode to allVisible and add the display mode button to the secondary/detail view controller to allow the user to switch between display modes. The displayModeButtonItem property of the split view controller returns a bar button item that you add to the navigation bar of the secondary view controller for this purpose.

I do both these steps in the application:didFinishLaunchingWithOptions: method of the application delegate (variable names chosen for brevity over readability):

if let svc = self.window?.rootViewController as? UISplitViewController {
  svc.preferredDisplayMode = .allVisible
  if let nc = svc.viewControllers.last as? UINavigationController {
    nc.topViewController?.navigationItem.leftBarButtonItem = svc.displayModeButtonItem
  }
}

Presenting the Secondary

Since I am using a Storyboard segue we will take care of configuring the secondary view controller in prepare(for:sender:). If you are not using segues you would perform similar steps in the target-action method used to create and present the view controller:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
   guard let navController = segue.destination as? UINavigationController,
        let viewController = navController.topViewController as? DetailViewController else {
            fatalError("Expected DetailViewController")
    }

    // Manage the display mode button
    viewController.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
    viewController.navigationItem.leftItemsSupplementBackButton = true

    // Configure the secondary view controller
    viewController.detailItem = Date()
}

The key step for the split view controller is to add the display mode button as a navigation item to the secondary view controller.

Collapsing the Secondary

There is one potential problem left. On initial startup in a horizontal compact size class such as the iPhone the split view controller collapses and will by default show the secondary view controller. This does not always make for a good user experience if we are depending on the user first doing something in the master view.

Luckily there is a delegate method we can use to control whether the collapsed split view shows the master or detail view controllers.

First we will set the master view controller as the delegate in viewDidLoad and add a boolean flag to keep track of when the secondary view controller has something to show:

class MasterViewController: UIViewController {
  fileprivate var collapseDetailViewController = true

  override func viewDidLoad() {
    super.viewDidLoad()
    splitViewController?.delegate = self
  }
  // ...
}

We can then add the split view controller delegate method we need in a class extension:

extension MasterViewController: UISplitViewControllerDelegate {
  func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
    return collapseDetailViewController
  }
}

Returning true from the splitViewController(_:collapseSecondary:onto:) delegate method stops the secondary view controller from being used in the collapsed interface. We can clear our flag the first time we segue/transition to the secondary:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
   collapseDetailViewController = false
   // ...
}

Now when the split view controller collapses it will show the primary view controller until we have set the detail item in the secondary controller.

Get The Code

What I hope you see with this example is how little code is required with a split view controller to get a highly adaptive user interface. The sample Xcode project includes both Swift and Objective-C versions if you want to take a closer look:

Never miss a post!

iOS Size Classes Cheat Sheet

Subscribe and get my free iOS Size Classes Cheat Sheet

Success! Now check your email to confirm your subscription and download your free guide to iOS Size Classes.

There was an error submitting your subscription. Please try again.

Unsubscribe at any time.
No time to watch WWDC videos?

Sign up to get my iOS posts direct to your inbox and I will send you a free PDF of my iOS Size Classes Cheat Sheet.

OK! Check your inbox (or spam folder) for an email to confirm your details and download your free guide to iOS Size Classes.

There was an error submitting your subscription. Please try again.

Unsubscribe at any time.
Archives Categories