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

Adopting hover support for Apple Pencil

At a glance

Item Summary
Purpose Shows a distance-sensitive Apple Pencil hover preview and commits drawn strokes to a persistent bitmap.
App architecture A storyboard-hosted HoverDrawView owns gesture, path, preview, and rendering state; its view controller is only a host.
Main patterns Custom view state owner, gesture recognizer adapter, two-phase rendering, and lifecycle-managed graphics resources.
Project style Small UIKit target with drawing types grouped under Drawing/.

Project structure

ApplePencilSampleApp/ApplePencilSampleApp/
├── AppDelegate.swift
├── SceneDelegate.swift
├── ViewController.swift
├── Drawing/
│   ├── HoverDrawView.swift
│   └── DrawGestureRecognizer.swift
└── Base.lproj/Main.storyboard

Structure observations

  • The empty host controller confirms that interaction logic belongs to the reusable view.
  • A custom long-press recognizer exists only to retain the current touch/event needed for coalesced Pencil samples.

Overall architecture

Reference code

ApplePencilSampleApp/ApplePencilSampleApp/Drawing/HoverDrawView.swift:65 — the view creates and retains both recognizers because they drive one shared drawing state machine.

private func configureGestureRecognizers() {
    if drawGestureRecognizer == nil {
        let drawGesture = DrawGestureRecognizer(target: self, action: #selector(drawGesture(_:)))
        drawGesture.minimumPressDuration = 0
        addGestureRecognizer(drawGesture)
        drawGestureRecognizer = drawGesture
    }
    if hoverGestureRecognizer == nil {
        let hoverGesture = UIHoverGestureRecognizer(target: self, action: #selector(hoverGesture(_:)))
        addGestureRecognizer(hoverGesture)
        hoverGestureRecognizer = hoverGesture
    }
}

Interpretation

Hover and draw update the same path/layer, but only completed drawing is flattened into the bitmap. The view guards hover while drawing so preview state cannot overwrite an active stroke.

Ownership and state

Ownership evidence

ApplePencilSampleApp/ApplePencilSampleApp/Drawing/HoverDrawView.swift:23 — all mutable drawing and preview state is private to the canvas.

private var bitmapContext: CGContext?
private var bitmapContextSize = CGSize(width: 0, height: 0)
private var bezierPath = UIBezierPath()
private var shapeLayer: CAShapeLayer?
private var drawGestureRecognizer: DrawGestureRecognizer?
private var hoverGestureRecognizer: UIHoverGestureRecognizer?
private var isPreviewing = false
private var previewAlpha = 1.0
private var isDrawing = false
private var strokePathTail: [CGPoint] = []
Owner Object or state Relationship Mutation authority
Storyboard hierarchy Host controller and drawing view Creates/retains UIKit lifecycle
HoverDrawView Recognizers, paths, bitmap context, shape layer, mode flags Creates and retains Private view methods
Custom recognizer Current touch/event Weak observation during a gesture Touch overrides/reset
View layer Flattened bitmap contents and live sublayer Retains presentation resources Canvas rendering methods/Core Animation

Class and protocol design

Type Responsibility Depends on
HoverDrawView Own input state, derive paths, render preview/live stroke, commit bitmap UIKit gestures, Core Graphics, Core Animation
DrawGestureRecognizer Expose current touch/event for coalesced input UILongPressGestureRecognizer
ViewController Host storyboard content UIViewController only

No app-defined protocol abstraction is present. Subclassing is used where UIKit requires custom view and gesture behavior.

Access control

Symbol Access Verified effect Likely rationale
Rendering constants/state/helpers private Only HoverDrawView can alter preview/drawing invariants or graphics resources. The view is the sole interaction/rendering owner.
DrawGestureRecognizer.currentTouch/currentEvent implicit internal weak Canvas can read them across files; recognizer does not prolong event/touch lifetime. Coalesced-touch bridge between recognizer and canvas.
maxPreviewZOffset / fadeZOffset implicit internal constants Same-module code could tune thresholds. Sample-visible tuning points.
Types implicit internal Target-local, not a reusable framework API. App sample boundary.

Reference code

ApplePencilSampleApp/ApplePencilSampleApp/Drawing/DrawGestureRecognizer.swift:13 — only the bridge values cross the two-type boundary, and both are weak.

class DrawGestureRecognizer: UILongPressGestureRecognizer {
    weak var currentTouch: UITouch?
    weak var currentEvent: UIEvent?

    override func reset() {
        super.reset()
        currentTouch = nil
        currentEvent = nil
    }
}

Logic ownership and placement

Logic Owning type or file Placement rationale
Gesture installation/teardown HoverDrawView view lifecycle Resources exist only while attached to a hierarchy.
Coalesced Pencil sample access DrawGestureRecognizer UIKit sends raw touch/event there.
Draw and hover state transitions HoverDrawView gesture handlers Both modes share path/layer state.
Live preview/stroke CAShapeLayer helpers Fast transient rendering.
Persistent strokes CGContext helpers Completed paths are flattened into bitmap content.

Design patterns

Pattern Source evidence Purpose or tradeoff
Custom view state owner ApplePencilSampleApp/ApplePencilSampleApp/Drawing/HoverDrawView.swift:11 Keeps input and rendering invariants below the controller.
Gesture recognizer adapter ApplePencilSampleApp/ApplePencilSampleApp/Drawing/DrawGestureRecognizer.swift:13 Exposes coalesced-touch context absent from a plain action callback.
Two-phase rendering ApplePencilSampleApp/ApplePencilSampleApp/Drawing/HoverDrawView.swift:157 Uses a live vector layer, then commits finished drawing to bitmap.
Lifecycle-managed resources ApplePencilSampleApp/ApplePencilSampleApp/Drawing/HoverDrawView.swift:48 Creates when attached and clears graphics resources when detached.

Naming conventions

  • HoverDrawView names both supported interaction modes; DrawGestureRecognizer names its focused adapter role.
  • Boolean state uses isPreviewing and isDrawing; thresholds use max... and fade....
  • Rendering commands use update, reset, and current to distinguish mutation from derivation.
  • Drawing/ groups the only feature-specific view and recognizer.

Architecture takeaways

  • Put tightly coupled input and rendering state in the custom canvas instead of a passive host controller.
  • Keep hover preview transient and commit only completed strokes to durable backing storage.
  • Use weak references when exposing UIKit-owned touch/event objects beyond an override.
  • Release bitmap/layer resources when the view leaves its hierarchy.

Source map

Source file Relevant symbols
ApplePencilSampleApp/ApplePencilSampleApp/Drawing/HoverDrawView.swift Input state machine and two-phase renderer
ApplePencilSampleApp/ApplePencilSampleApp/Drawing/DrawGestureRecognizer.swift Current touch/event adapter
ApplePencilSampleApp/ApplePencilSampleApp/ViewController.swift Passive host controller
ApplePencilSampleApp/ApplePencilSampleApp/Base.lproj/Main.storyboard View hierarchy composition