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

Adding hardware keyboard support to your app

At a glance

Item Summary
Purpose Demonstrates global key commands, raw key presses, modifier-aware gestures, standard edit actions, and keyboard focus.
App architecture A scene-owned split controller composes file-list and canvas responders; AppDelegate publishes global commands whose selectors travel down UIKit’s responder chain.
Main patterns Responder-chain routing, optional capability protocol, weak child-to-parent delegates, diffable data sources, and in-memory model store.
Project style Programmatic UIKit target split into model, file list, root split controller, and Canvas/ feature files.

Project structure

ProIllustratorSample/
├── AppDelegate.swift
├── SceneDelegate.swift
├── MainViewController.swift
├── FileListViewController.swift
├── GlobalKeyboardShortcutRespondable.swift
├── Model.swift
└── Canvas/
    ├── CanvasViewController.swift
    ├── AddShapeViewController.swift
    ├── ShapeView.swift
    └── ShapeSelectionView.swift

Structure observations

  • Root composition is programmatic; no storyboard owns the screen graph.
  • The global-command protocol is separate because AppDelegate defines commands while active child responders implement effects.
  • Raw-key and gesture movement share the canvas’s selection/movement state.

Overall architecture

Reference code

ProIllustratorSample/MainViewController.swift:11 — the split controller owns both children and translates file selection into canvas state.

class MainViewController: UISplitViewController, FileListViewControllerDelegate {
    let fileListViewController = FileListViewController(style: .plain)
    let canvasViewController = CanvasViewController(nibName: nil, bundle: nil)

    override init(nibName: String?, bundle: Bundle?) {
        super.init(nibName: nil, bundle: nil)
        fileListViewController.delegate = self
        viewControllers = [UINavigationController(rootViewController: fileListViewController),
                           UINavigationController(rootViewController: canvasViewController)]
    }
}

Interpretation

UIKit’s responder chain is the command bus. The app publishes selectors globally, but the first active child that conforms and can perform the action becomes the handler; the root does not manually switch on focus.

Ownership and state

Ownership evidence

ProIllustratorSample/FileListViewController.swift:15 — the list owns its store/data source but delegates selection without retaining the root.

class FileListViewController: UITableViewController {
    var store = FileStore()
    weak var delegate: FileListViewControllerDelegate?
    var tableViewDataSource: UITableViewDiffableDataSource<Int, Int>?
}
Owner Object or state Relationship Mutation authority
Scene delegate Main split controller and window Creates/retains root; UIKit owns scene lifetime Scene connection
Main split controller File-list and canvas controllers Creates and retains Root composition and selection delegate
File-list controller FileStore, diffable source, selected rows Owns List commands and callbacks
Canvas controller Selected file, shape views, selection, movement timer/direction Receives model reference; owns presentation/interaction state Canvas input handlers
Add-shape controller Shape data source Owns; reports choice through weak delegate Add-shape screen

Class and protocol design

Type or protocol Responsibility Depends on
GlobalKeyboardShortcutRespondable Optional Objective-C selectors for global create/delete commands UIKit responder chain
FileListViewControllerDelegate Report selected IllustrationFile Main split controller
AddShapeViewControllerDelegate Report selected shape style Canvas controller
CanvasViewController Handle raw presses, modifier gestures, selection, and shape rendering UIPress, gesture recognizer, model/view types
FileStore Create, find, list, and delete illustration files In-memory array

The optional @objc capability is deliberate: each responder implements only commands meaningful in its current context, and canPerformAction controls availability.

Access control

Symbol Access Verified effect Likely rationale
AddShapeViewController.delegate public weak Exposes assignment broadly but does not retain the canvas. Weakness prevents presentation/delegate cycles; public is wider than this internal app type requires.
Canvas movement/selection helpers private Only canvas input methods can hit-test, start timers, or alter internal selection mechanics. Keep interaction invariants local.
addKeyCommands fileprivate Same-file code may invoke it, but other files cannot. File-scoped setup helper; private would also fit current use.
shapeDataSource private Only add-shape controller configures and queries identifiers. Protect list implementation.
Models and controllers implicit internal Available throughout the app target, not exported as a library. Target-local collaboration.

Reference code

ProIllustratorSample/Canvas/CanvasViewController.swift:195 — raw-key translation and movement mechanics remain private to the canvas.

private func movementDirectionForKeyPresses(_ presses: Set<UIPress>) -> UIRectEdge? {
    guard let key = presses.first?.key else { return nil }
    switch key.keyCode {
        case .keyboardLeftArrow: return .left
        case .keyboardDownArrow: return .bottom
        case .keyboardUpArrow: return .top
        case .keyboardRightArrow: return .right
        default: return nil
    }
}

Logic ownership and placement

Logic Owning type or file Placement rationale
Global menu command declaration AppDelegate.buildMenu Application menu customization entry point.
Command capability selectors GlobalKeyboardShortcutRespondable Decouples command publication from active handler.
Child composition and file-selection routing MainViewController Has both sides of the split.
File CRUD and snapshot updates FileListViewController + FileStore List owns store and stable identifier presentation.
Raw presses, gestures, shape movement CanvasViewController Shares one selection and movement state machine.

Design patterns

Pattern Source evidence Purpose or tradeoff
Responder-chain command routing ProIllustratorSample/AppDelegate.swift:32 Global shortcuts reach the focused capable child without root dispatch code.
Capability protocol ProIllustratorSample/GlobalKeyboardShortcutRespondable.swift:19 Names optional command selectors shared by otherwise different screens.
Composite/root controller ProIllustratorSample/MainViewController.swift:11 Owns the split and mediates selection between children.
Weak delegate ProIllustratorSample/Canvas/AddShapeViewController.swift:10 Returns modal selection without retaining its presenter.
Diffable data source ProIllustratorSample/FileListViewController.swift:35 Separates stable model identifiers from table update mechanics.

Naming conventions

  • ...Respondable names a responder-chain capability; delegate protocols name the source controller.
  • User commands use standard responder verbs: createNewItem, deleteSelectedItem, and selectAll.
  • Input-state helpers use directional verbs such as startMoving... and stopMoving....
  • Canvas/ groups the view, controller, and modal needed by one feature.

Architecture takeaways

  • Put global shortcuts in the application menu, but let focus and the responder chain choose the handler.
  • Use canPerformAction to express contextual command availability.
  • Keep raw-key state, gesture state, and model commits in the same interaction owner.
  • Use weak delegates for child-to-parent events and strong ownership from parent to child.

Source map

Source file Relevant symbols
ProIllustratorSample/AppDelegate.swift Global menu commands
ProIllustratorSample/SceneDelegate.swift Programmatic root ownership
ProIllustratorSample/MainViewController.swift Split composition and selection routing
ProIllustratorSample/GlobalKeyboardShortcutRespondable.swift Optional command capability
ProIllustratorSample/FileListViewController.swift Store, diffable source, and command handling
ProIllustratorSample/Canvas/CanvasViewController.swift Raw keyboard and gesture interaction
ProIllustratorSample/Model.swift File and shape models