Integrating pointer interactions into your iPad app
At a glance
| Item |
Summary |
| Purpose |
Demonstrates button pointer providers, custom regions/shapes/effects, modifier keys, hover gestures, context menus, and trackpad scrolling. |
| App architecture |
One screen controller coordinates interactions; custom views own geometry/state, while capability extensions separate pointer, button-provider, and context-menu callbacks. |
| Main patterns |
Controller adapter, polymorphic shape strategy, capability extensions, and custom-control encapsulation. |
| Project style |
Storyboard feature screen with behavior divided by UIKit interaction contract rather than by traditional service layers. |
Project structure
PointerInteraction/
├── ViewController.swift
├── ViewController+PointerInteraction.swift
├── ViewController+ButtonProvider.swift
├── ViewController+ContextMenu.swift
├── ShapeView.swift
├── AlphaControl.swift
├── AppDelegate.swift
├── SceneDelegate.swift
└── Base.lproj/Main.storyboard
Structure observations
ViewController.swift owns assembly and gestures; three extensions isolate unrelated interaction contracts.
- Shape subclasses share hit testing and preview construction in
ShapeView, overriding only path geometry.
AlphaControl contains its drawing, labels, pan-to-alpha logic, and preview path in one custom control.
Overall architecture
flowchart LR
Storyboard["Storyboard views"] --> Controller["ViewController"]
Controller --> Buttons["UIButton pointerStyleProvider"]
Controller --> Shapes["ShapeView instances"]
Controller --> Alpha["AlphaControl"]
Shapes --> Pointer["UIPointerInteraction"]
Alpha --> Pointer
Pointer --> Delegate["ViewController pointer extension"]
Delegate --> Region["UIPointerRegion"]
Delegate --> Style["UIPointerStyle"]
Shapes --> Gestures["pan / tap / pinch / hover"]
Shapes --> Context["UIContextMenuInteraction"]
Context --> MenuExtension["context-menu extension"]
Reference code
PointerInteraction/ViewController.swift:24 — the controller installs interactions, but asks each custom view for the geometry used by those interactions.
// Set the alpha control's color, and add a pointer interaction.
alphaControl.currentColor = UIColor.systemOrange
alphaControl.addInteraction(UIPointerInteraction(delegate: self))
setupShapeView(shapeView: ovalShape, color: UIColor.systemRed, label: "Lift")
setupShapeView(shapeView: roundRectShape, color: UIColor.systemGreen, label: "Highlight")
setupShapeView(shapeView: triangleShape, color: UIColor.systemGray, label: "Hover")
setupShapeView(shapeView: squareShape, color: UIColor.systemBlue, label: "Automatic")
Interpretation
The controller is the shared UIKit delegate because it coordinates several view types, but it does not duplicate geometry. ShapeView.targetedPreview(), innerPath, and AlphaControl.highlightRegion keep visual knowledge at the view boundary.
Ownership and state
classDiagram
ViewController o-- ShapeView : storyboard outlets
ViewController o-- AlphaControl : storyboard outlet
ShapeView <|-- TriangleShapeView
ShapeView <|-- RoundrectShapeView
ShapeView <|-- OvalShapeView
ShapeView *-- UIBezierPath : outer and inner paths
ShapeView *-- UILabel : effect label
AlphaControl *-- UILabel : value and color labels
AlphaControl *-- UIView : swatch
ViewController ..|> UIPointerInteractionDelegate
ViewController ..|> UIContextMenuInteractionDelegate
ViewController ..|> UIGestureRecognizerDelegate
Ownership evidence
PointerInteraction/ShapeView.swift:10 — the base view owns reusable visual geometry and effect label state that its subclasses specialize.
class ShapeView: UIView {
// ...
}
| Owner |
State |
Relationship |
Mutation authority |
| Storyboard/controller |
Shape/control instances |
Storyboard creates; controller configures |
Setup and gesture callbacks |
ShapeView |
Paths, colors, effect name/label |
Owns view geometry |
Base/subclass drawing and controller gestures |
AlphaControl |
Current color/alpha and child labels/swatch |
Owns custom-control state |
Pan gesture and setup methods |
| Controller extensions |
No separate stored state |
Same controller split across files |
UIKit callbacks |
Class and protocol design
| Type |
Responsibility |
Design note |
ViewController |
Assemble interactions and coordinate gestures/modifiers |
Shared adapter for several UIKit protocols |
ShapeView |
Draw path, hit-test, build targeted preview |
Base strategy with reusable geometry contract |
| Shape subclasses |
Override pathViewForFrame(_:) |
Polymorphism is limited to shape generation |
AlphaControl |
Render and edit alpha using direct touch or continuous scroll |
Self-contained UIControl subclass |
ButtonPointerEffectKind |
Map storyboard button tags to styles |
Target-local enum in button-provider extension |
There is no app-defined protocol. UIKit delegate protocols are applied in capability-specific extensions; subclassing provides the one app-defined polymorphic seam for shape geometry.
Access control
| Symbol |
Access |
Verified effect |
Rationale |
ShapeView.imageView and innerInset |
private |
Subclasses cannot depend on internal child-view layout. |
Base view protects its rendering implementation. |
AlphaControl constants and labels |
private |
External code changes only semantic color/alpha properties. |
Maintains custom-control layout invariants. |
| Controller outlets/helpers and shape geometry |
implicit internal |
Extensions in separate files and storyboard feature code share them. |
Capability file split requires target-level visibility. |
| Types |
implicit internal |
App target only. |
No public framework is produced. |
fileprivate / public |
none |
No file-only or exported API surface is declared. |
Collaboration is either private-to-type or target-local. |
Reference code
PointerInteraction/AlphaControl.swift:12 — semantic state is target-visible for setup, while child views and drawing constants remain private.
var currentColor = UIColor.gray
var currentAlpha: CGFloat = 1.0
static let regionIdentifier: AnyHashable = "colorPath"
private let marginSpacing: CGFloat = 6.0
private let cornerRadius: CGFloat = 12.0
private var colorSwatch: UIView!
private var colorLabel: UILabel!
private var valueLabel: UILabel!
Logic ownership and placement
| Logic |
Owner or file |
Why it belongs there |
| Interaction/gesture assembly |
ViewController.swift |
One screen coordinates all interactive components. |
| Region/style/enter-exit callbacks |
+PointerInteraction.swift |
Direct implementation of one UIKit contract. |
| Button tag-to-style mapping |
+ButtonProvider.swift |
Specific to UIButton.pointerStyleProvider. |
| Context-menu actions/previews |
+ContextMenu.swift |
Separate capability sharing shape previews. |
| Path generation/hit testing |
ShapeView hierarchy |
Must agree with drawn geometry. |
| Alpha scrolling/drawing |
AlphaControl |
Custom control owns its displayed value and child views. |
Design patterns
| Pattern |
Source evidence |
Purpose or tradeoff |
| Controller as interaction adapter |
PointerInteraction/ViewController+PointerInteraction.swift:10 |
Reuses one delegate across heterogeneous views. |
| Strategy by subclass |
PointerInteraction/ShapeView.swift:87 |
Specializes geometry through one override. |
| Capability extensions |
PointerInteraction/ViewController+ContextMenu.swift:10 |
Keeps callback families readable while sharing controller state. |
| Custom control encapsulation |
PointerInteraction/AlphaControl.swift:10 |
Couples semantic value, gestures, preview path, and rendering coherently. |
| Tag-to-enum mapping |
PointerInteraction/ViewController+ButtonProvider.swift:11 |
Turns storyboard integer tags into named effect cases. |
Naming conventions
- Shape subclasses name geometry directly:
TriangleShapeView, RoundrectShapeView, and OvalShapeView.
- Capability files use
ViewController+... suffixes that match UIKit features.
targetedPreview, highlightRegion, and regionIdentifier use pointer/context-menu vocabulary consistently.
setup..., pan..., tap..., scale..., and hover... distinguish configuration from gesture commands.
Architecture takeaways
- Keep pointer preview paths and hit-testing geometry in the custom view that draws the shape.
- Split one interaction-heavy controller by framework capability when callbacks share the same state owner.
- Use a small override point when visual variants differ only in geometry.
- Expose semantic control state while keeping child views and layout constants private.
Source map
| Source file |
Relevant symbols |
PointerInteraction/ViewController.swift |
Assembly and gestures |
PointerInteraction/ViewController+PointerInteraction.swift |
Regions, styles, enter/exit animation |
PointerInteraction/ViewController+ButtonProvider.swift |
Button style provider |
PointerInteraction/ViewController+ContextMenu.swift |
Context menu and targeted preview |
PointerInteraction/ShapeView.swift |
Shape hierarchy, drawing, hit testing |
PointerInteraction/AlphaControl.swift |
Scrollable alpha custom control |