Sample CodeiOS, iPadOS, Mac CatalystReviewed 2026-07-21View on Apple Developer

Adding menus and shortcuts to the menu bar and user interface

At a glance

Item Summary
Purpose Builds Mac Catalyst menu-bar commands, keyboard shortcuts, contextual menus, and a native toolbar around a split-view editor.
App architecture AppDelegate owns main-menu construction/validation; split-view controllers own item state and responder actions; Catalyst toolbar behavior is isolated in an extension.
Main patterns Menu builder/factory, responder-chain commands, weak detail delegate, platform adapter extension, and preferences observer.
Project style Storyboard UIKit target with primary/detail controllers and separate table/toolbar extension files.

Project structure

Menus/
├── AppDelegate.swift
├── SceneDelegate.swift
├── MenuController.swift
├── PrimaryViewController.swift
├── PrimaryViewController+Table.swift
├── PrimaryViewController+Toolbar.swift
├── DetailViewController.swift
├── TableItem.swift
└── Base.lproj/Main.storyboard

Structure observations

  • MenuController constructs command values; command targets remain on responders such as AppDelegate and the primary controller.
  • Primary controller extensions separate table mechanics and Catalyst-only toolbar integration without splitting state ownership.
  • Detail-to-primary mutations travel through a weak protocol delegate.

Overall architecture

Reference code

Menus/AppDelegate.swift:37 — application-level menu construction delegates the command tree to MenuController and keeps validation state in AppDelegate.

override func buildMenu(with builder: UIMenuBuilder) {
    if builder.system == .main {
        menuController = MenuController(with: builder)
        fontMenuStyleStates.insert(MenuController.FontStyle.plain.rawValue)
    }
}

override func validate(_ command: UICommand) {
    if let styles = command.propertyList as? [String: String],
       let style = styles[MenuController.CommandPListKeys.StylesIdentifierKey] {
        command.state = fontMenuStyleStates.contains(style) ? .on : .off
    }
}

Interpretation

Menu definitions and effects are intentionally separated. The builder installs commands with selectors and property-list payloads; UIKit resolves selectors through AppDelegate or the current responder, while validation projects current state back onto commands.

Ownership and state

Ownership evidence

Menus/PrimaryViewController.swift:16 — the primary owns item state and lazily resolves the storyboard-owned detail, then installs itself as weak delegate.

var tableItems = [AnyModelItem]()

lazy var detailViewController: DetailViewController = {
    var result = DetailViewController()
    if let navigation = splitViewController?.viewControllers[1] as? UINavigationController,
       let detail = navigation.topViewController as? DetailViewController {
        result = detail
        result.detailItemDelegate = self
    }
    return result
}()
Owner Object or state Relationship Mutation authority
AppDelegate Menu helper and checked font styles Creates/retains Menu build, actions, validation
Storyboard split controller Primary/detail navigation stacks Creates/retains UIKit/storyboard lifecycle
Primary controller Model items, selection, formatter, Catalyst toolbar references Owns Table and command actions
Detail controller Current item and preference-observation token Receives item; owns observation Detail edits/observer callbacks
Detail controller DetailItemDelegate Weak reference to primary Primary implements mutations

Class and protocol design

Type or protocol Responsibility Depends on
MenuController Define menu identifiers, commands, hierarchies, key equivalents, and payloads UIMenuBuilder and selector targets
AppDelegate Build main menu, validate app-level command state, receive global actions UIApplicationDelegate
PrimaryViewController Own item collection and responder actions Table, detail delegate, toolbar extension
DetailItemDelegate Cut/copy/paste/delete and persist detail changes Primary controller
DetailViewController Present/edit one item, contextual menu, print, preferences Weak delegate, UserDefaults, UIContextMenuInteractionDelegate
AnyModelItem Represent date-or-text rows and sharing item providers UIActivityItemsConfigurationReading

Access control

Symbol Access Verified effect Likely rationale
PrimaryViewController.selectRow private Selection mechanics stay inside the primary file/type. Prevent external code from bypassing table coordination.
DetailViewController.detailItemDelegate implicit internal weak Target code may wire it, without retaining primary. Storyboard collaborators need assignment; weak avoids a cycle.
Menu factories and identifiers implicit internal AppDelegate and primary can share them across files. Command construction spans multiple responders.
Catalyst toolbar extension compile-time #if targetEnvironment(macCatalyst) Toolbar symbols do not exist in non-Catalyst builds. Platform boundary is stronger than runtime branching.
Most state implicit internal Same-target extensions can collaborate. Extension-based organization trades narrow lexical privacy for file separation.

Reference code

Menus/DetailViewController.swift:20 — the app-defined collaboration is target-local, and the stored delegate is weak.

protocol DetailItemDelegate: NSObjectProtocol {
    func performCutAction()
    func performCopyAction()
    func performPasteAction()
    func performDeleteAction()
    func didUpdateItem(_ item: AnyModelItem)
}

weak var detailItemDelegate: DetailItemDelegate?

Logic ownership and placement

Logic Owning type or file Placement rationale
Menu hierarchy and key-command factories MenuController Pure command construction grouped away from effects.
App-wide action/validation state AppDelegate Main menu system asks application and delegate.
Row CRUD and responder validation Primary controller Owns item collection and selection.
Detail rendering, contextual actions, printing Detail controller Operations act on current detail item/view.
Native toolbar construction/actions PrimaryViewController+Toolbar.swift Catalyst-specific adapter around primary commands.
Split collapse policy SceneDelegate Scene-level container behavior.

Design patterns

Pattern Source evidence Purpose or tradeoff
Menu builder/factory Menus/MenuController.swift:83 Centralizes declarative menu topology and command payloads.
Responder chain Menus/MenuController.swift:139 Selectors target the active capable controller without hard references from the menu tree.
Command validation Menus/AppDelegate.swift:54 Keeps checked/disabled state synchronized with app state.
Weak delegate Menus/DetailViewController.swift:20 Returns detail commands to the collection owner without a retain cycle.
Platform adapter extension Menus/PrimaryViewController+Toolbar.swift:10 Keeps Catalyst-only NSToolbar code out of cross-platform table logic.
Observer Menus/DetailViewController.swift:58 Projects preference changes into the visible detail automatically.

Naming conventions

  • MenuController is a construction helper, while action methods end in Action and match selectors.
  • Nested enums (Cities, Tools, FontStyle, Arrows) model finite menu groups; CommandPListKeys names payload keys.
  • +Table and +Toolbar filenames identify extensions of the same owner.
  • Toolbar identifiers use ...ID; Boolean availability is expressed through responder validation rather than stored is... flags.

Architecture takeaways

  • Separate menu topology from command effects, then let selectors and responder routing connect them.
  • Validate commands from the same owner that holds their state.
  • Compile platform-specific integrations out of unsupported targets and keep them in focused extensions.
  • Use weak delegates when a detail screen edits data owned by its primary/list screen.

Source map

Source file Relevant symbols
Menus/AppDelegate.swift Main-menu build, actions, and validation
Menus/MenuController.swift Menu/command factories and identifiers
Menus/PrimaryViewController.swift Item state and responder actions
Menus/PrimaryViewController+Table.swift Table data-source/delegate mechanics
Menus/PrimaryViewController+Toolbar.swift Catalyst toolbar adapter
Menus/DetailViewController.swift Weak delegate, context menu, printing, preferences
Menus/SceneDelegate.swift Split-view configuration/collapse policy