1 · Introduction

Scope & Goal

In AppKit, NSViewController and its subclasses form the structural backbone of macOS user-interface architecture. This guide delivers a comprehensive, example-driven exploration of AppKit view controllers—from bare-bones lifecycle management to advanced, multi-pane container controllers—equipping you with practical fluency for real-world projects.

2 · NSViewController Essentials

2.1 Creation & Lifecycle

The canonical flow mirrors UIKit, but with macOS-specific nuances:

  1. init(nibName:bundle:) or SwiftUI bridging via NSHostingController
  2. loadView() (override to create views programmatically)
  3. viewDidLoad() – one-time setup after view graph
  4. viewWillAppear() / viewDidAppear()
  5. viewWillDisappear() / viewDidDisappear()

2.2 Minimal Example (Swift)


final class DashboardController: NSViewController {

    override func loadView() {
        self.view = NSView()
        view.wantsLayer = true
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let label = NSTextField(labelWithString: "Hello, Dashboard")
        label.font = .systemFont(ofSize: 24, weight: .semibold)
        label.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(label)
        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}

3 · Specialised Container Controllers

3.1 NSSplitViewController

Provides resizable split panes; ideal for Finder-like layouts.


let splitVC = NSSplitViewController()

let sidebar = NSViewController()
sidebar.view = NSView(frame: .zero)
sidebar.title = "Sidebar"

let content = DashboardController()
content.title = "Main"

splitVC.addSplitViewItem(NSSplitViewItem(sidebarWithViewController: sidebar))
splitVC.addSplitViewItem(NSSplitViewItem(viewController: content))

3.2 NSTabViewController

Manages horizontal tab interface. macOS 11+ uses unified toolbar style automatically.

3.3 NSPageController

A page-flipping, swipe-driven container (think Preview’s thumbnails).

3.4 NSCollectionViewController

If you migrate from UIKit’s UICollectionViewController, adopt NSCollectionView plus a vanilla NSViewController or subclass.

4 · Inter-Controller Communication

4.1 Delegation & NotificationCenter

On macOS, loose coupling remains king. Use NotificationCenter.default.post(name:object:userInfo:) to broadcast model changes, or adopt delegate protocols for one-to-one conversations.

4.2 Storyboard Segues vs Programmatic Composition

Storyboards allow drag-and-drop relationships. Pure code offers version-control clarity. The hybrid approach is common: layout in Interface Builder, dynamic wiring in viewDidLoad().

5 · Case Study: Split-View Finder-Clone

5.1 Objective

Build an app featuring a folder sidebar and a file list that updates reactively.

5.2 Key Steps

  1. Create SidebarController handling NSOutlineView of folders.
  2. Create FilesController with NSTableView.
  3. Wire via NSSplitViewController.
  4. Publish path changes with Notification.Name("PathChanged").

5.3 Selected Snippet


extension Notification.Name { static let PathChanged = Self("PathChanged") }

final class SidebarController: NSViewController, NSOutlineViewDelegate {
    /* ... */
    func outlineViewSelectionDidChange(_ notification: Notification) {
        guard let path = selectedPath else { return }
        NotificationCenter.default.post(name: .PathChanged,
                                        object: self,
                                        userInfo: ["path": path])
    }
}

final class FilesController: NSViewController {
    @objc private func reload(_ note: Notification) {
        if let path = note.userInfo?["path"] as? URL {
            files = try? FileManager.default.contentsOfDirectory(at: path,
                                      includingPropertiesForKeys: nil)
            tableView.reloadData()
        }
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        NotificationCenter.default.addObserver(self,
            selector: #selector(reload(_:)),
            name: .PathChanged, object: nil)
    }
}

6 · Method Reference

6.1 NSViewController Core Methods

Method Description Important Parameters
init(nibName:bundle:) Initialises with a xib file. nibName: Optional String of the XIB.
bundle: Bundle? containing the nib.
loadView() Override to assign self.view manually. None—must set view property.
viewWillAppear() Invoked just before view is added to a window. animated does not exist in AppKit.
representedObject Generic property for model injection. Any Swift type.
prepare(for segue: NSStoryboardSegue, sender: Any?) Fired before storyboard transition. segue: Supplies destination VC.
sender: The origin initiator.

6.2 NSSplitViewItem Factory Methods

sidebarWithViewController(_ vc) Creates a collapsible sidebar item. Width obeys sidebar metrics.
contentListWithViewController(_ vc) For master-detail lists (macOS 13+ style).

6.3 NSTabViewController API

addTabViewItem(_:) Adds a tab; item holds its own view controller.
selectedTabViewItemIndex Get/set currently visible tab index.

7 · Best Practices & Tips

7.1 Avoid Massive View Controllers

Extract business logic into model layers; leverage Swift packages.

7.2 Auto Layout Debugging

Use NSView.layoutSubtreeIfNeeded() and NSView.fittingSize to interrogate constraints.

7.3 Storyboard Identifiers

Declare identifiers as static constants to prevent typos.

7.4 Touch Bar & Toolbar Integration

From viewWillAppear(), populate NSTouchBar or adopt modern NSToolbarItemGroup.