Enhancing your iPad app with pointer interactions
At a glance
| Item |
Summary |
| Purpose |
Applies custom pointer regions, shapes, and effects to a quilt editor, buttons, and color swatches. |
| App architecture |
ViewController coordinates tools, while QuiltView owns stitch state, persistence, drawing, taps, and its editor pointer interaction. |
| Main patterns |
Custom view state owner, weak selection delegate, per-component pointer strategies, and view composition. |
| Project style |
Storyboard UIKit app with pointer behavior colocated beside the view whose geometry it describes. |
Project structure
QuiltSimulator/
├── ViewController.swift
├── QuiltView.swift
├── ThreadPickerViewController.swift
├── PatchworkView.swift
├── ColorBlockView.swift
├── AppDelegate.swift
├── SceneDelegate.swift
└── Base.lproj/Main.storyboard
Structure observations
- The main screen coordinates menus and tool selection but does not store stitch locations.
- The thread picker contains its cell because the cell’s lift effect is local to that feature.
PatchworkView and ColorBlockView build the decorative background independently from the editable stitch overlay.
Overall architecture
flowchart LR
Main["ViewController"] --> Quilt["QuiltView"]
Main --> Picker["ThreadPickerViewController"]
Picker -->|weak delegate color| Main
Main -->|stitchColor| Quilt
Quilt --> Locations["stitchLocations"]
Quilt --> ShapeLayer["CAShapeLayer"]
Quilt --> Defaults["UserDefaults"]
Quilt --> Pointer["UIPointerInteractionDelegate"]
Picker --> Cells["ThreadCell pointer delegates"]
Main --> ButtonProviders["UIButton pointerStyleProvider"]
Quilt *-- Patchwork["PatchworkView"]
Patchwork *-- Blocks["ColorBlockView"]
Reference code
QuiltSimulator/QuiltView.swift:62 — the editor view installs both its drawing input and pointer adapter with the state they affect.
func sharedInit() {
let patchworkView = PatchworkView()
patchworkView.translatesAutoresizingMaskIntoConstraints = false
self.insertSubview(patchworkView, at: 0)
patchworkView.centerXAnchor.constraint(equalTo: self.safeAreaLayoutGuide.centerXAnchor).isActive = true
patchworkView.centerYAnchor.constraint(equalTo: self.safeAreaLayoutGuide.centerYAnchor).isActive = true
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:))))
stitchLayer.lineWidth = 2
stitchLayer.fillColor = UIColor.clear.cgColor
stitchLayer.lineCap = .round
self.layer.addSublayer(stitchLayer)
self.addInteraction(UIPointerInteraction(delegate: self))
}
Interpretation
Pointer support is an enhancement of existing components, not a central service. Each component calculates a style from its own bounds and purpose, while the controller only provides button-specific closures and tool routing.
Ownership and state
classDiagram
ViewController *-- UIButton : thread tool
ViewController o-- QuiltView : storyboard outlet
ViewController --> ThreadPickerViewController : presents
ThreadPickerViewController o-- ThreadPickerViewControllerDelegate : weak
ThreadPickerViewController *-- ThreadCell : collection cells
QuiltView *-- CAShapeLayer : creates
QuiltView *-- CGPoint : stitch locations
QuiltView *-- PatchworkView : creates
PatchworkView *-- ColorBlockView : creates
QuiltView ..|> UIPointerInteractionDelegate
ThreadCell ..|> UIPointerInteractionDelegate
Ownership evidence
QuiltSimulator/ThreadPickerViewController.swift:10 — selection leaves the picker through a class-bound weak delegate rather than direct knowledge of the main screen.
protocol ThreadPickerViewControllerDelegate: NSObjectProtocol {
func threadPickerDidPickColor(_ threadPicker: ThreadPickerViewController, color: UIColor)
}
| Owner |
State |
Relationship |
Mutation authority |
QuiltView |
Mode, color, stitch points, dash pattern, shape layer |
Owns editor model/presentation |
Tap/removal APIs and property setters |
ViewController |
Tool button and screen outlets |
Coordinates |
User actions and picker callback |
ThreadPickerViewController |
Color choices |
Owns fixed picker data |
Collection selection |
ThreadCell |
Swatch and pointer interaction |
UIKit owns reused cells |
Cell configuration/layout |
Class and protocol design
| Type |
Responsibility |
Design note |
QuiltView |
Persist editor preferences, collect stitches, draw path, provide crosshair pointer |
Cohesive feature view, though model and rendering are combined |
ViewController |
Configure tools/buttons/menus and apply chosen color |
Screen coordinator |
ThreadPickerViewController |
Present choices and emit one semantic selection event |
Reusable child flow through a weak delegate |
ThreadCell |
Circular swatch and matching lift preview |
Cell-local pointer geometry |
PatchworkView / ColorBlockView |
Compose decorative quilt background |
Composite view hierarchy |
ThreadPickerViewControllerDelegate is the only app-defined protocol. It reduces coupling to one UIColor result. Pointer and scroll behavior use UIKit protocols because those callbacks are driven by the framework.
Access control
| Symbol |
Access |
Verified effect |
Rationale |
| User-default key constants |
private static |
Only QuiltView can address its persisted settings. |
Storage keys are implementation details. |
Picker delegate |
implicit internal weak |
The presenter assigns it without creating a retain cycle. |
Cross-type collaboration inside the app target. |
| Stitch state and drawing helpers |
implicit internal |
Other target code could access them, although only QuiltView currently mutates most. |
Sample favors inspectable code over a narrow library API. |
| Outlets and controller helpers |
implicit internal |
Storyboard and same-target code can reach them. |
No framework boundary is being exported. |
| Types |
implicit internal |
Not visible outside the app module. |
App-local sample architecture. |
Reference code
QuiltSimulator/QuiltView.swift:12 — persisted-key names are private, while the view exposes semantic mode and color properties to its controller.
private static let straightLineStitchUserDefaultsKey = "useStrightLineStitch"
private static let colorUserDefaultsKey = "threadColor"
var useStraightLineStitch: Bool {
get { UserDefaults.standard.bool(forKey: QuiltView.straightLineStitchUserDefaultsKey) }
set { UserDefaults.standard.set(newValue,
forKey: QuiltView.straightLineStitchUserDefaultsKey) }
}
Logic ownership and placement
| Logic |
Owner |
Why it belongs there |
| Stitch capture, line construction, and crosshair region |
QuiltView |
All depend on editor bounds and stitch state. |
| Button pointer styles |
ViewController |
Styles are configured with controller-created toolbar buttons. |
| Swatch lift preview |
ThreadCell |
Preview path depends on the reused cell’s swatch. |
| Color selection routing |
Picker protocol and ViewController |
Picker emits intent; screen applies it to editor/tool UI. |
| Patchwork construction |
PatchworkView / ColorBlockView |
Visual composition has no editor-state dependency. |
Design patterns
| Pattern |
Source evidence |
Purpose or tradeoff |
| Custom view state owner |
QuiltSimulator/QuiltView.swift:15 |
Keeps editor input, preferences, and drawing coherent but makes the view stateful. |
| Weak delegate |
QuiltSimulator/ThreadPickerViewController.swift:17 |
Sends semantic output without coupling picker to presenter. |
| Pointer style strategy |
QuiltSimulator/ViewController.swift:56 |
Uses closures for buttons and delegates for custom views/cells. |
| Composite view |
QuiltSimulator/PatchworkView.swift:14 |
Builds repeated blocks from nested stack views. |
Naming conventions
- Types name visual roles:
QuiltView, PatchworkView, ColorBlockView, and ThreadCell.
- Commands use domain verbs such as
ripLastStitch, ripAllStitches, and threadPickerDidPickColor.
useStraightLineStitch and stitchColor expose user concepts rather than storage keys.
sharedInit signals the common initializer path for programmatic and storyboard creation.
Architecture takeaways
- Put pointer-region calculations beside the view geometry they describe.
- Use a weak, semantic delegate when a presented picker needs to return one result.
- Let the screen controller coordinate tools while the custom editor owns its durable interaction state.
- Compose decorative views separately when they do not participate in editing behavior.
Source map
| Source file |
Relevant symbols |
QuiltSimulator/ViewController.swift |
Tool and button pointer coordination |
QuiltSimulator/QuiltView.swift |
Stitch state, persistence, rendering, crosshair pointer |
QuiltSimulator/ThreadPickerViewController.swift |
Picker delegate and cell lift effect |
QuiltSimulator/PatchworkView.swift |
Patchwork composition |
QuiltSimulator/ColorBlockView.swift |
Reusable striped block |