Navigating Hierarchical Data Using Outline and Split Views
At a glance
| Item | Summary |
|---|---|
| Purpose | Present file-like hierarchy in a source-list outline and swap an appropriate detail view in a split view. |
| Architecture | Node model + NSTreeController/outline controller + split/detail controllers, coordinated through notifications and child containment. |
| Scope | Also demonstrates editing, menus, drag/drop, expansion, and restoration through extensions on the outline controller. |
Project structure
SourceView/
├── Node.swift
├── OutlineViewController.swift
├── OutlineViewController+{Delegate,Helpers,DragDrop,Expansion,Restoration}.swift
├── SplitViewController.swift
├── WindowViewController.swift
└── {Icon,File,Image}ViewController.swift
The large outline controller is split by framework capability. Detail controllers remain small and presentation-specific.
Overall architecture
flowchart LR
Data["plist + file system"] --> Node
Node --> Tree["NSTreeController"]
Tree --> Outline["OutlineViewController"]
Outline --> Notice["selection notification"]
Notice --> Split["SplitViewController"]
Split --> Choice["viewControllerForSelection"]
Choice --> Detail["Icon / File / Image / Multi detail"]
Reference code
SourceView/SplitViewController.swift:80 — the split controller reacts to selection and asks the outline owner for the correct detail controller.
@objc
private func handleSelectionChange(_ notification: Notification) {
// ...
}Ownership and state
classDiagram
OutlineViewController *-- NSTreeController : outlet
NSTreeController o-- Node : arranged tree
OutlineViewController *-- NSViewController : cached detail choices
SplitViewController *-- NSViewController : active detail child
WindowViewController o-- NSTreeNode : selectedNodes snapshot
Ownership evidence
SourceView/OutlineViewController.swift:46 — one controller owns observation and the reusable detail-controller set.
private var treeControllerObserver: NSKeyValueObservation?
@objc dynamic var contents: [AnyObject] = []
private var iconViewController: IconViewController!
private var fileViewController: FileViewController!
private var imageViewController: ImageViewController!The tree controller mutates arranged Node objects. The outline controller owns hierarchy commands and cached detail candidates. The split controller owns only the currently embedded child relationship; the window controller snapshots selection for command validation.
Class and protocol design
| Type | Responsibility |
|---|---|
Node / NodeType |
Codable tree data and derived file/display capabilities. |
OutlineViewController |
Populate, select, edit, add, remove, drag, drop, and map selection to detail. |
SplitViewController |
Replace the active detail child while preserving split-view behavior. |
WindowViewController |
Toolbar commands, progress, and menu validation. |
| Detail controllers | Render a selected URL, image, icon collection, or multiple selection. |
Most protocols are AppKit contracts (NSTextFieldDelegate, validation, outline delegates). CustomMenuDelegate is the small app-defined seam for contextual menu construction.
Access control
| Boundary | Effect and rationale |
|---|---|
private outlets, observers, helper methods, and cached detail controllers |
Keeps tree and child-controller invariants with their owner. |
@objc dynamic contents and Node.children |
Makes model state visible to Cocoa bindings/KVO; it remains internal to the module. |
Implicit internal types and cross-file extensions |
Lets the feature be divided into capability files without exporting a library API. |
| Private toolbar identifier extension | Prevents raw UI identifiers from becoming general dependencies. |
SourceView/Node.swift:19 shows the internal model and dynamic children property; SourceView/WindowViewController.swift:188 shows the file-private identifier namespace.
Logic ownership and placement
| Logic | Owner |
|---|---|
| Tree population and mutations | OutlineViewController and helpers |
| Row display, contextual menu, drag/drop, restoration | Named OutlineViewController+... extensions |
| Detail selection policy | OutlineViewController.viewControllerForSelection |
| Child embedding/removal | SplitViewController |
| Toolbar/menu enablement | WindowViewController |
| File-derived display properties | Node and URL helpers |
Design patterns
| Pattern | Evidence | Purpose |
|---|---|---|
| MVC | SourceView/Node.swift:19, SourceView/OutlineViewController.swift:11 |
Separates hierarchy data from AppKit coordination. |
| Observer | SourceView/OutlineViewController.swift:483 |
Converts tree selection KVO into feature-level notification. |
| Child view controller | SourceView/SplitViewController.swift:55 |
Swaps detail content without replacing the split controller. |
| Capability extensions | SourceView/OutlineViewController+DragDrop.swift:11 |
Organizes a large framework-facing controller by concern. |
| Codable adapter | SourceView/OutlineViewController.swift:307 |
Decodes plist hierarchy into Node values. |
Naming conventions
NodeTypeand derivedis.../can...properties express hierarchy capability.*ViewControllernames map directly to screen roles;+Capabilityfiles map to AppKit behaviors.- Notification names are scoped in nested
NotificationNamesstructures rather than scattered strings.
Architecture takeaways
- Let the outline controller own hierarchy policy and the split controller own containment mechanics.
- Cache reusable detail controllers, but keep only one actively embedded.
- Translate low-level KVO into a feature event at a clear boundary.
- Split framework-heavy controllers by capability while retaining one state owner.
Source map
| Source | Role |
|---|---|
SourceView/Node.swift:19 |
Tree model |
SourceView/OutlineViewController.swift:11 |
Hierarchy owner |
SourceView/SplitViewController.swift:19 |
Detail containment |
SourceView/WindowViewController.swift:10 |
Window command UI |
SourceView/FileViewController.swift:20 |
Representative detail binding |