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

Illustrating the force, altitude, and azimuth properties of touch input

At a glance

Item Summary
Purpose Visualizes Pencil/finger force, location, altitude, azimuth, coalesced samples, predictions, and estimated-property updates.
App architecture The controller receives UIKit touch callbacks; CanvasView converts each touch stream into Line models and renders them, while ReticleView presents current diagnostics.
Main patterns Input pipeline, identity map, transient-versus-frozen rendering, option-set sample tags, and protocol-based formatting.
Project style Storyboard host with custom view/model files; no separate service or persistence layer.

Project structure

TouchCanvas/
├── ViewController.swift
├── CanvasView.swift
├── Line.swift
├── ReticleView.swift
├── Formatters.swift
├── SeparatorView.swift
├── AppDelegate.swift
└── Base.lproj/Main.storyboard

Structure observations

  • Raw touch routing stays in the controller, but stroke lifecycle and rendering state stay in CanvasView.
  • Line.swift groups the aggregate, point value, OptionSet tags, update matching, and drawing behavior.
  • Reticle geometry and high-frequency label formatting are isolated from the canvas pipeline.

Overall architecture

Reference code

TouchCanvas/CanvasView.swift:125 — each event replaces stale predictions, then appends coalesced and new predicted samples to the line for that touch identity.

func drawTouches(_ touches: Set<UITouch>, withEvent event: UIEvent?) {
    var updateRect = CGRect.null

    for touch in touches {
        let line: Line = activeLines.object(forKey: touch) ?? addActiveLineForTouch(touch)

        updateRect = updateRect.union(line.removePointsWithType(.predicted))

        let coalescedTouches = event?.coalescedTouches(for: touch) ?? []
        let coalescedRect = addPointsOfType(.coalesced, for: coalescedTouches,
                                            to: line, in: updateRect)
        updateRect = updateRect.union(coalescedRect)

        let predictedTouches = event?.predictedTouches(for: touch) ?? []
        let predictedRect = addPointsOfType(.predicted, for: predictedTouches,
                                            to: line, in: updateRect)
        updateRect = updateRect.union(predictedRect)
    }

    setNeedsDisplay(updateRect)
}

Interpretation

Predictions are disposable latency hints, while coalesced/standard points become durable line data. Estimated properties create a third phase: an ended touch may remain in pendingLines until UIKit supplies final values.

Ownership and state

Ownership evidence

TouchCanvas/CanvasView.swift:25 — the canvas privately owns the complete line lifecycle and its frozen rendering cache.

private var needsFullRedraw = true

/// Array containing all line objects that need to be drawn in `drawRect(_:)`.
private var lines = [Line]()

/// Array containing all line objects that have been completely drawn into the frozenContext.
private var finishedLines = [Line]()

private let activeLines: NSMapTable<UITouch, Line> = NSMapTable.strongToStrongObjects()
private let pendingLines: NSMapTable<UITouch, Line> = NSMapTable.strongToStrongObjects()
Owner State Relationship Mutation authority
CanvasView Active, pending, live, and finished lines; frozen context/image Creates and retains Touch pipeline and draw callbacks
Line Live/committed points and estimation-index lookup Owns aggregate state Canvas calls semantic add/update/commit methods
LinePoint Captured touch properties and point tags Owned by a line Initializer and estimated-update method
ReticleView Actual/predicted angles, vectors, and layers Controller supplies current values Property observers/layout

Class and protocol design

Type Responsibility Design note
CanvasView Correlate touch identities, manage line phases, and render efficiently Feature state owner below controller
Line Aggregate points, apply estimated updates, draw/commit segments Model with rendering behavior
LinePoint / PointType Store one sample and orthogonal classification flags OptionSet avoids an exploding state enum
ReticleView Render actual and predicted orientation diagnostics Presentation-only custom view
DisplayFormat Give numeric geometry values stable-width text Small app-defined capability protocol

DisplayFormat is genuine protocol-oriented design: unrelated Foundation/Core Graphics scalar types gain the same read-only presentation capability through extensions, with no wrapper hierarchy.

Access control

Symbol Access Verified effect Rationale
Canvas line maps/cache/helpers private Only CanvasView can move a line through active, pending, committed, and finished phases. Protects lifecycle and redraw invariants.
Line point arrays/update map private Callers use semantic methods instead of mutating point storage. Preserves estimation-index consistency.
Reticle layers/math helpers mostly private Controller sets only diagnostic inputs and two predicted layers it must hide/show. Hides rendering implementation while exposing required collaboration.
Formatters file-level private Protocol conformances in the same file reuse them; other files cannot. High-frequency formatting policy is file-local.
Types/protocol implicit internal Shared only inside the sample target. No public framework surface.

Reference code

TouchCanvas/Formatters.swift:10 — the protocol is target-visible, but the formatter object supporting each conformance is file-private.

protocol DisplayFormat {
    var valueFormattedForDisplay: String? { get }
}

private let vectorFormatter: NumberFormatter = {
    let formatter = NumberFormatter()
    formatter.allowsFloats = true
    formatter.minimumFractionDigits = 3
    formatter.maximumFractionDigits = 3
    formatter.positivePrefix = "+"
    return formatter
}()

Logic ownership and placement

Logic Owner Why it belongs there
Raw responder callbacks and diagnostic routing ViewController UIKit delivers touches to the controller and it owns labels/reticle.
Touch-to-line correlation and phase changes CanvasView Requires active/pending maps and redraw decisions.
Estimated-property matching Line and LinePoint Estimation indexes identify model samples, not UI elements.
Frozen/live drawing CanvasView plus Line Canvas owns contexts; line knows segment appearance.
Numeric label formatting Formatters.swift Reusable across vector, point, and scalar diagnostics.

Design patterns

Pattern Source evidence Purpose or tradeoff
Input pipeline TouchCanvas/CanvasView.swift:125 Separates event expansion from sample modeling and drawing.
Identity map TouchCanvas/CanvasView.swift:40 Correlates non-NSCopying UITouch objects to line aggregates.
Two-tier rendering TouchCanvas/CanvasView.swift:81 Draws stable work from a frozen bitmap and only live lines each frame.
Option-set tagging TouchCanvas/Line.swift:285 A point can simultaneously be predicted, pending, finger input, or cancelled.
Protocol extension formatting TouchCanvas/Formatters.swift:26 Shares display semantics without a model superclass.

Naming conventions

  • activeLines, pendingLines, and finishedLines name lifecycle phases directly.
  • Line, LinePoint, and nested PointType express aggregate-to-value containment.
  • Update methods say what changed: updateEstimatedPropertiesForTouches and updateWithTouch.
  • The source uses gague... for gauge outlets/helpers; architecture notes retain the source spelling only when naming symbols.

Architecture takeaways

  • Treat predicted input as replaceable state and final samples as durable model state.
  • Keep an explicit pending phase when framework values may arrive after touch end.
  • Correlate UIKit identity objects at the custom-view boundary, then pass stable model values deeper.
  • Protect rendering caches and sample indexes with private ownership and semantic mutation methods.

Source map

Source file Relevant symbols
TouchCanvas/ViewController.swift Touch routing, labels, and reticle
TouchCanvas/CanvasView.swift Line lifecycle and frozen/live renderer
TouchCanvas/Line.swift Line aggregate, samples, estimation updates
TouchCanvas/ReticleView.swift Orientation visualization
TouchCanvas/Formatters.swift DisplayFormat protocol and conformances