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

Adopting menus and UIActions in your user interface

At a glance

Item Summary
Purpose Demonstrates menus and UIAction on buttons, bar items, segmented controls, custom controls, deferred elements, and visible-menu updates.
App architecture A root custom menu control navigates to independent programmatic demo controllers; each screen owns one menu API variation.
Main patterns Action-as-value, example catalog, custom control, deferred provider, and lifecycle-scoped timer.
Project style One UIKit target with one controller per demo and no separate domain model.

Project structure

ControlMenus/
├── ViewController.swift
├── CustomMenuControl.swift
├── ButtonMenuDemoViewController.swift
├── BarButtonItemDemoViewController.swift
├── SegmentedControlDemoViewController.swift
├── DeferredMenuElementDemoViewController.swift
├── UpdateMenuViewController.swift
├── AppDelegate.swift
└── SceneDelegate.swift

Structure observations

  • UIAction values carry titles, state, and closures directly; there is no command-model layer.
  • CustomMenuControl is both reusable navigation control and the most complete custom-menu example.
  • Asynchronous and live-update lifecycles stay in their dedicated demo controllers.

Overall architecture

Reference code

ControlMenus/ViewController.swift:15 — the root expresses navigation as action values installed on one custom menu control.

let customMenuControl = CustomMenuControl(
    frame: .zero,
    primaryAction: UIAction(title: "Demo Selector") { _ in })
customMenuControl.showSelectedItem = false
customMenuControl.items = [
    UIAction(title: "Button Demo") { [unowned self] _ in
        self.navigationController?.pushViewController(ButtonMenuDemoViewController(), animated: true)
    },
    UIAction(title: "Deferred Element Demo") { [unowned self] _ in
        self.navigationController?.pushViewController(DeferredMenuElementDemoViewController(), animated: true)
    }
]

Interpretation

The sample treats actions as composable values: the same object can describe a menu item and execute navigation or UI effects. Demo screens remain intentionally independent so their UIKit configurations are easy to compare.

Ownership and state

Ownership evidence

ControlMenus/CustomMenuControl.swift:19 — the control owns source actions and selection state; changing them refreshes or disables its menu.

var items: [UIAction] = [] {
    didSet {
        if items.isEmpty {
            contextMenuInteraction?.dismissMenu()
            isContextMenuInteractionEnabled = false
        } else {
            updateMenuIfVisible()
            isContextMenuInteractionEnabled = true
        }
    }
}

var selectedIndex: Int = -1 {
    didSet { updateMenuIfVisible() }
}
Owner Object or state Relationship Mutation authority
Root controller/view hierarchy Selector control Creates/adds; hierarchy retains Root setup
Custom control Actions, selected index, display policy, private label Strongly owns Property observers and proxy actions
Each demo controller Its controls/menu values Creates/adds Local action closures
Update demo Context-menu interaction and timer Retains; invalidates timer on menu end Framework lifecycle callbacks
Deferred element Async completion contract UIKit retains provider closure Provider must call completion

Class and protocol design

Type Responsibility Depends on
CustomMenuControl Turn source actions into selectable proxy actions, update visible menus, animate presentation UIControl context-menu behavior
Root ViewController Build the demo selector and push screens Navigation controller
Button/bar/segment demo controllers Show built-in menu attachment points UIKit controls
Deferred demo Append async menu children UIDeferredMenuElement
Update demo / UIContextMenuInteractionDelegate Adapt rich/compact presentation and mutate visible actions over time Interaction/timer

No app-defined protocol exists; UIAction closures and UIKit delegate methods provide the collaboration boundaries.

Access control

Symbol Access Verified effect Likely rationale
CustomMenuControl.titleLabel private Callers configure the control through actions/state, not label internals. Preserve custom-control rendering authority.
Control items and selection implicit internal Root controller can configure them across files. Intended target-level API of the custom control.
Demo controllers and helpers implicit internal Available within the app target only. Catalog app, not framework.
[unowned self] action captures nonowning capture Actions do not retain their owning controller. Avoid cycles while assuming actions do not outlive screens.

Reference code

ControlMenus/CustomMenuControl.swift:54 — the private lazy label is created only through the control’s own rendering path.

private lazy var titleLabel: UILabel = {
    let label = UILabel()
    label.textColor = tintColor
    label.highlightedTextColor = .secondaryLabel
    addSubview(label)
    return label
}()

Logic ownership and placement

Logic Owning type or file Placement rationale
Catalog navigation Root controller Knows all demonstration screens.
Selection/proxy action/menu update Custom control Defines the reusable control contract.
Built-in control configurations Corresponding demo controller Keeps each UIKit variation isolated.
Async child production Deferred demo Owns completion timing.
Live action state/visibility updates Update demo Owns interaction and timer lifecycle.

Design patterns

Pattern Source evidence Purpose or tradeoff
Action-as-value ControlMenus/ViewController.swift:17 Co-locates menu metadata and behavior; closures require capture discipline.
Custom control ControlMenus/CustomMenuControl.swift:13 Packages menu selection, appearance, and target-action forwarding.
Proxy action ControlMenus/CustomMenuControl.swift:97 Adds selection state while forwarding the original command.
Deferred provider ControlMenus/DeferredMenuElementDemoViewController.swift:21 Loads dynamic children only when presentation needs them.
Lifecycle timer ControlMenus/UpdateMenuViewController.swift:78 Updates only while the menu is visible, then invalidates.

Naming conventions

  • Demo types end in DemoViewController; names identify the UIKit API under study.
  • items, selectedIndex, and showSelectedItem form the custom control’s small state API.
  • Factory/forwarding methods use proxyAction, menu, and updateMenuIfVisible.

Architecture takeaways

  • Model menu commands as values when metadata and effect naturally travel together.
  • Keep a custom control’s subviews private and expose semantic items/selection instead.
  • Scope timers and deferred work to menu presentation lifecycle.
  • Treat catalog screens as independent examples, not a forced application-layer hierarchy.

Source map

Source file Relevant symbols
ControlMenus/ViewController.swift Action-driven demo navigation
ControlMenus/CustomMenuControl.swift Custom control state, proxy actions, visible updates
ControlMenus/ButtonMenuDemoViewController.swift UIButton menu/action variations
ControlMenus/DeferredMenuElementDemoViewController.swift Async menu elements
ControlMenus/UpdateMenuViewController.swift Presentation adaptation and live updates