Sample CodeiOS 14.0+, iPadOS 14.0+, Mac Catalyst 14.0+Reviewed 2026-07-21View on Apple Developer

Accessibility design for Mac Catalyst

At a glance

Item Summary
Purpose Adapt a UIKit coffee browser for keyboard, VoiceOver, semantic groups, focus groups, and Mac Catalyst conventions.
Architecture Small UIKit MVC app with value-type view models and controller-owned screen state.
Main pattern A cell delegate sends interaction intent to the list controller, which owns mutation and reapplies a diffable snapshot.
Boundary One app target; there is no extension, service layer, or reusable framework target.

Project structure

RoastedBeans/
├── Model/                 # Coffee, Status, RBViewModel, sample data
├── RBListViewController.swift
├── RBCell.swift
├── RBDetailViewController.swift
├── RBStatusView.swift
└── AppDelegate.swift / SceneDelegate.swift

The folders separate data shapes from UIKit screens, but they do not create independent modules.

Overall architecture

Reference code

RoastedBeans/RBListViewController.swift:56 — the list maps its controller-owned models into cells (abridged).

datasource = UITableViewDiffableDataSource(
    tableView: tableView,
    cellProvider: { [self] table, index, _ in
        let data = self.tableData[index.row]
        cell.configure(for: data)
        cell.delegate = self
        return cell
    }
)

Ownership and state

Ownership evidence

RoastedBeans/RBCell.swift:9 and RoastedBeans/RBCell.swift:22 — the child reports actions without owning the controller.

protocol RBCellActionProtocol: AnyObject {
    func didIncreaseRating(cell: RBCell)
    func didToggleFavorite(cell: RBCell)
}

weak var delegate: RBCellActionProtocol?

The list controller is the mutation authority for tableData; the detail controller receives and mutates its own value copy. There is no shared persistence layer.

Class and protocol design

RBListViewController and RBDetailViewController are final UIKit subclasses. RBViewModel is a Hashable struct suitable for diffable identity. The app-defined RBCellActionProtocol: AnyObject is a narrow protocol-oriented boundary for cell events; RBListViewController adopts it in an extension at RoastedBeans/RBListViewController.swift:96.

Access control

Symbol Access Why it fits
tableData, datasource private Only the list controller may mutate and publish its snapshot (RoastedBeans/RBListViewController.swift:11).
Cell outlets and formatting helpers private Storyboard wiring and presentation details stay inside RBCell (RoastedBeans/RBCell.swift:24).
delegate internal, weak The sibling controller must assign it; weak ownership avoids a cell/controller cycle.
App types internal by default A single app target has no public API surface.

No public or fileprivate API is needed. private is used for implementation details; default internal access joins files within the target.

Logic ownership and placement

Logic Owner Placement reason
Rating/favorite mutation and snapshot refresh RBListViewController It owns collection state and table coordination.
Cell accessibility value/actions RBCell They derive directly from cell presentation (RoastedBeans/RBCell.swift:147).
Detail semantic groups and focus identifiers RBDetailViewController They describe that screen’s custom hierarchy (RoastedBeans/RBDetailViewController.swift:60).
Sample construction SampleData Static fixture generation stays next to model types (RoastedBeans/Model/RBViewModel.swift:105).

Design patterns

Pattern Evidence Tradeoff
Delegate Cell action protocol and weak reference Keeps reusable cell interaction-free, with some controller coupling through RBCell.
Diffable data source Snapshot reapplied after mutation Stable identity and simple UI refresh; the controller remains state-heavy.
View model value RBViewModel combines domain and display state Convenient for a sample, but detail changes do not flow back automatically.
Controller coordinator List creates and shows detail Appropriate for two screens; no separate router abstraction.

Naming conventions

  • The RB prefix scopes app-specific UIKit types (RBCell, RBStatusView, RBViewModel).
  • Event methods start with did...; configuration methods start with configure... or setup....
  • Model nouns are singular; collections use plural names such as coffees, statuses, and locationsAvailable.

Architecture takeaways

  • Put mutation in the controller that owns the diffable snapshot.
  • Use a weak, class-bound delegate for reusable child-view actions.
  • Keep accessibility semantics next to the custom presentation they describe.
  • This is a compact single-target teaching app, not a reusable Catalyst architecture package.

Source map

Source Role
RoastedBeans/Model/RBViewModel.swift:10 Value view model and identity
RoastedBeans/RBListViewController.swift:136 Favorite mutation and snapshot
RoastedBeans/RBCell.swift:187 Accessibility custom actions
RoastedBeans/RBDetailViewController.swift:141 Custom navigation semantic group
RoastedBeans/AppDelegate.swift:9 App entry point