Organize Your User Interface with a Stack View
At a glance
| Item | Summary |
|---|---|
| Purpose | Build a vertically stacked inspector whose independent sections expand, collapse, animate, and restore state. |
| Architecture | Three small protocols compose a host, header, and body into StackItemContainer; concrete view controllers supply each role. |
| Main owner | The root ViewController loads section controllers and inserts their header/body views into NSStackView. |
Project structure
InfoBarStackView/
├── StackView.swift
├── ViewController.swift
├── BaseViewController.swift
├── HeaderViewController.swift
├── {Form,Table,Collection,Other}ViewController.swift
└── WindowController.swift
StackView.swift holds the reusable protocol vocabulary and default behavior; feature controllers provide concrete content.
Overall architecture
flowchart LR
Root["ViewController / StackItemHost"] --> Load["storyboard section controllers"]
Load --> Body["BaseViewController / StackItemBody"]
Body --> Container["StackItemContainer"]
Container --> Header["StackItemHeader"]
Container --> Body
Root --> Stack["NSStackView arranged subviews"]
Header --> Stack
Body --> Stack
Reference code
InfoBarStackView/ViewController.swift:27 — the host composes each protocol-backed section into arranged views.
private func addViewController(withIdentifier identifier: String) {
// ...
}Ownership and state
classDiagram
ViewController *-- NSStackView : outlet
BaseViewController *-- StackItemContainer : lazy
StackItemContainer *-- StackItemHeader : strong role
StackItemContainer *-- StackItemBody : strong role
StackItemContainer *-- NSControlStateValue : disclosure state
StackItemHeader --> ViewController : disclosure closure
Ownership evidence
InfoBarStackView/BaseViewController.swift:28 — every body lazily creates the container that pairs it with a header.
class BaseViewController: NSViewController, StackItemBody {
// ...
// MARK: - StackItemContainer
// ...
}The root controller owns visual arrangement. Each body controller owns its lazy container and original height. The container owns the disclosure state and role references; the header carries a closure that sends disclosure intent back to the host.
Class and protocol design
| Type | Responsibility |
|---|---|
StackItemHost |
Toggle containers and coordinate body/header state. |
StackItemHeader |
Expose a header controller, disclosure callback, and visual state update. |
StackItemBody |
Expose a body controller and show/hide behavior. |
StackItemContainer |
Pair header and body and store disclosure state. |
BaseViewController |
Common body behavior, height state, and restoration. |
| Concrete body controllers | Table, collection, form, or other section content. |
Protocol extensions provide the default orchestration and constrain header/body adapters to NSViewController where needed.
Access control
| Boundary | Effect and rationale |
|---|---|
private addViewController |
Only the root composition flow can insert sections into the stack. |
Protocols and containers are implicit internal |
They are reusable across the app target but not presented as a public framework. |
| Body state is target-visible | Protocol default implementations and concrete subclasses need height and container access. |
| Restored state passes through controller methods | AppKit’s restoration contract remains the external boundary. |
InfoBarStackView/ViewController.swift:27 is the explicit private composition method; InfoBarStackView/StackView.swift:13 shows the internal protocol surface.
Logic ownership and placement
| Logic | Owner |
|---|---|
| Disclosure state transition | StackItemHost default implementation |
| Height animation | StackItemBody default implementation |
| Header visual update | Concrete StackItemHeader |
| Storyboard composition and arranged-subview order | Root ViewController |
| Save/restore disclosure state | BaseViewController |
| Window recreation | WindowController |
Design patterns
| Pattern | Evidence | Purpose |
|---|---|---|
| Protocol composition | InfoBarStackView/StackView.swift:13 |
Decouples host, header, and body roles. |
| Container object | InfoBarStackView/StackView.swift:122 |
Carries paired roles and their shared state. |
| Template method via base class | InfoBarStackView/BaseViewController.swift:17 |
Lets subclasses supply titles while reusing lifecycle behavior. |
| Closure callback | InfoBarStackView/ViewController.swift:36 |
Sends header intent to the host without a concrete header dependency. |
| State restoration | InfoBarStackView/BaseViewController.swift:55 |
Persists each section’s disclosure state. |
Naming conventions
- Protocol names describe roles rather than implementations:
StackItemHost,StackItemHeader, andStackItemBody. StackItemContainerstates that it composes roles;BaseViewControllersignals subclass reuse.- Concrete content controllers use domain-neutral view type names such as
FormViewControllerandTableViewController.
Architecture takeaways
- Use narrow role protocols when composition matters more than concrete controller types.
- Store shared header/body state in a container, not in
NSStackViewitself. - Put reusable show/hide behavior in constrained protocol extensions.
- Keep storyboard loading and arranged-subview order in one composition owner.
Source map
| Source | Role |
|---|---|
InfoBarStackView/StackView.swift:13 |
Protocol roles and container |
InfoBarStackView/ViewController.swift:10 |
Stack composition owner |
InfoBarStackView/BaseViewController.swift:10 |
Reusable body and restoration |
InfoBarStackView/WindowController.swift:10 |
Window restoration |