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:
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
:
Property | Type | Description |
---|---|---|
viewControllers | [UIViewController] | The array of view controllers (primary at index 0, secondary at index 1). |
preferredDisplayMode | UISplitViewController.DisplayMode | The preferred arrangement of the primary and secondary view controllers. |
displayMode | UISplitViewController.DisplayMode | The current display mode (read-only). |
primaryViewController | UIViewController? | The primary view controller (read-only). |
presentsWithGesture | Bool | Whether the user can swipe to show/hide the primary view controller. |
preferredPrimaryColumnWidthFraction | CGFloat | The preferred width of the primary column as a fraction of the split view’s width (0.0 to 1.0). |
minimumPrimaryColumnWidth | CGFloat | The minimum width of the primary column. |
maximumPrimaryColumnWidth | CGFloat | The maximum width of the primary column. |
delegate | UISplitViewControllerDelegate? | Delegate for handling split view events, such as collapse or separation. |
showsSecondaryOnly | Bool | If 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:
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:
Method | Description |
---|---|
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:
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:
Method | Description |
---|---|
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:
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:
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:
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:
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.swiftsplitViewController.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 andviewControllers
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
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
}
}