Skip to content

UISplitViewController

UISplitViewController is a UIKit class that manages a two-pane interface, typically displaying a primary (master) view controller and a secondary (detail) view controller side by side or in a stacked configuration, depending on the device and orientation. It is a subclass of UIViewController and is commonly used in iPad apps or iPhone apps with adaptive layouts to present hierarchical content, such as a list and its details. This document covers the key properties, methods, and usage of UISplitViewController, along with examples and best practices.

Overview of UISplitViewController

UISplitViewController provides a flexible, adaptive interface for presenting two view controllers: a primary view controller (often a list or menu) and a secondary view controller (often details or content). On iPads, it typically shows both panes side by side in landscape orientation, while on iPhones or in compact environments, it stacks them with navigation. It supports various display modes and is highly customizable for different screen sizes and orientations.

Creating a UISplitViewController

You can create a UISplitViewController programmatically or via Interface Builder (storyboards/xibs). It requires at least one view controller, typically two (primary and secondary).

Programmatic Example:

swift
import UIKit

let primaryVC = UIViewController()
let secondaryVC = UIViewController()
let splitViewController = UISplitViewController()
splitViewController.viewControllers = [primaryVC, secondaryVC]
window?.rootViewController = splitViewController

Key Properties of UISplitViewController

Below are the most commonly used properties of UISplitViewController:

PropertyTypeDescription
viewControllers[UIViewController]The array of view controllers (primary at index 0, secondary at index 1).
preferredDisplayModeUISplitViewController.DisplayModeThe preferred arrangement of the primary and secondary view controllers.
displayModeUISplitViewController.DisplayModeThe current display mode (read-only).
primaryViewControllerUIViewController?The primary view controller (read-only).
presentsWithGestureBoolWhether the user can swipe to show/hide the primary view controller.
preferredPrimaryColumnWidthFractionCGFloatThe preferred width of the primary column as a fraction of the split view’s width (0.0 to 1.0).
minimumPrimaryColumnWidthCGFloatThe minimum width of the primary column.
maximumPrimaryColumnWidthCGFloatThe maximum width of the primary column.
delegateUISplitViewControllerDelegate?Delegate for handling split view events, such as collapse or separation.
showsSecondaryOnlyBoolIf true, shows only the secondary view controller (iOS 14+).

Display Modes

  • .automatic: Adapts based on context (default).
  • .primaryOverlay: Primary view overlays the secondary view.
  • .primaryHidden: Primary view is hidden.
  • .twoBesideSecondary: Primary and secondary views are side by side (iOS 14+).
  • .oneBesideSecondary: Similar to .twoBesideSecondary but with a single-column primary view (iOS 14+).
  • .twoOverSecondary: Primary view slides over the secondary view (iOS 14+).
  • .twoDisplaceSecondary: Primary view displaces the secondary view (iOS 14+).

Example: Configuring Properties:

swift
splitViewController.preferredDisplayMode = .oneBesideSecondary
splitViewController.preferredPrimaryColumnWidthFraction = 0.3
splitViewController.minimumPrimaryColumnWidth = 200
splitViewController.presentsWithGesture = true

Key Methods of UISplitViewController

UISplitViewController provides methods to manage its view controllers and display:

MethodDescription
show(_:sender:)Shows a view controller, typically the secondary one.
showDetailViewController(_:sender:)Shows a view controller as the secondary view controller.
hide(_:sender:)Hides a column (iOS 14+).
setViewController(_:for:)Sets a view controller for a specific column (iOS 14+).

Example: Showing a Detail View Controller:

swift
let detailVC = UIViewController()
splitViewController.showDetailViewController(detailVC, sender: nil)

UISplitViewControllerDelegate Methods

The UISplitViewControllerDelegate protocol handles adaptive behavior, such as collapsing or separating the primary and secondary view controllers:

MethodDescription
splitViewController(_:collapseSecondary:onto:)Determines if the secondary view controller should collapse onto the primary.
splitViewController(_:separateSecondaryFrom:)Returns the secondary view controller when separating from the primary.
splitViewController(_:show:viewController:sender:)Customizes how a view controller is shown.
splitViewController(_:displayModeForExpandingTo:)Specifies the display mode when expanding (iOS 14+).

Example: Implementing Delegate:

swift
splitViewController.delegate = self

extension ViewController: UISplitViewControllerDelegate {
    func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
        return true // Collapse secondary onto primary in compact environments
    }
}

Configuring the Split View

Setting View Controllers

Assign primary and secondary view controllers to viewControllers. Often, the primary view controller is a UINavigationController for a list, and the secondary is a UINavigationController for details.

Example:

swift
let primaryNav = UINavigationController(rootViewController: PrimaryViewController())
let secondaryNav = UINavigationController(rootViewController: SecondaryViewController())
splitViewController.viewControllers = [primaryNav, secondaryNav]

Display Mode Button

Add a display mode button to toggle the primary view controller’s visibility in the secondary view controller’s navigation bar.

Example:

swift
secondaryNav.topViewController?.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
secondaryNav.topViewController?.navigationItem.leftItemsSupplementBackButton = true

Adaptive Behavior

UISplitViewController adapts to different trait environments:

  • Regular Environment (e.g., iPad landscape): Shows primary and secondary side by side.
  • Compact Environment (e.g., iPhone or iPad portrait): Stacks view controllers, showing only one at a time with navigation.

Use the delegate to customize behavior in compact environments.

Example: Preventing Collapse:

swift
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
    return false // Prevent collapse, keep secondary view controller
}

Best Practices

  • Use Navigation Controllers: Wrap primary and secondary view controllers in UINavigationController for hierarchical navigation.

  • Support Accessibility: Set accessibilityLabel for view controllers and bar button items.

    swift
    splitViewController.accessibilityLabel = "Split view"
  • Use Auto Layout: Ensure subviews in view controllers use constraints (e.g., with SnapKit).

  • Handle Compact Environments: Test behavior on iPhones or iPad portrait to ensure proper collapse/separation.

  • Test Display Modes: Verify appearance with different preferredDisplayMode settings across devices.

  • Optimize for iPad Multitasking: Support Slide Over and Split View by handling trait collection changes.

Troubleshooting

  • Secondary View Not Showing: Ensure showDetailViewController(_:sender:) is called and viewControllers includes a secondary view controller.
  • Display Mode Button Missing: Verify displayModeButtonItem is added to the secondary view controller’s navigation item.
  • Layout Issues: Check safe area insets and preferredPrimaryColumnWidthFraction for proper column sizing.
  • Collapse Behavior Incorrect: Implement UISplitViewControllerDelegate methods to customize collapse/separation logic.
  • Performance Issues: Avoid heavy subviews in primary/secondary view controllers.

Example: Complete UISplitViewController Setup

swift
import UIKit
import SnapKit

class PrimaryViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemGray6
        navigationItem.title = "Primary"
        
        let button = UIButton(type: .system)
        button.setTitle("Show Detail", for: .normal)
        button.addTarget(self, action: #selector(showDetail), for: .touchUpInside)
        view.addSubview(button)
        
        button.snp.makeConstraints { make in
            make.center.equalToSuperview()
        }
    }
    
    @objc func showDetail() {
        let detailVC = SecondaryViewController()
        splitViewController?.showDetailViewController(UINavigationController(rootViewController: detailVC), sender: nil)
    }
}

class SecondaryViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        navigationItem.title = "Detail"
        
        let label = UILabel()
        label.text = "Detail Content"
        label.textAlignment = .center
        view.addSubview(label)
        
        label.snp.makeConstraints { make in
            make.center.equalToSuperview()
        }
        
        // Add display mode button
        navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
        navigationItem.leftItemsSupplementBackButton = true
    }
}

// App setup
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
        
        let splitViewController = UISplitViewController()
        let primaryNav = UINavigationController(rootViewController: PrimaryViewController())
        let secondaryNav = UINavigationController(rootViewController: SecondaryViewController())
        
        splitViewController.viewControllers = [primaryNav, secondaryNav]
        splitViewController.preferredDisplayMode = .oneBesideSecondary
        splitViewController.preferredPrimaryColumnWidthFraction = 0.3
        splitViewController.delegate = splitViewController as? UISplitViewControllerDelegate
        
        window?.rootViewController = splitViewController
        window?.makeKeyAndVisible()
        return true
    }
}

Resources

Released under the MIT License.