Supporting gesture interaction in your apps
At a glance
| Item | Summary |
|---|---|
| Purpose | Combine standard recognizers with a custom gesture-recognizer state machine and simultaneous-recognition policy. |
| App architecture | ViewController coordinates pan, pinch, rotation, and a custom ResetGestureRecognizer; the custom recognizer owns touch history and publishes state transitions back through UIKit target-action. |
| Main patterns | Gesture recognizer state machine, Target-action coordination, Framework delegate policy |
| Project style | Four Swift files; one feature controller and one custom UIGestureRecognizer subclass. |
Project structure
Source bundle/
├── GestureRecognizer/
│ ├── AppDelegate.swift
│ ├── SceneDelegate.swift
│ ├── ViewController.swift
│ ├── ResetGestureRecognizer.swift
│ ├── Base.lproj/
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ └── Info.plist
├── Configuration/
│ └── SampleCode.xcconfig
└── GestureRecognizer.xcodeproj/
├── .xcodesamplecode.plist
└── project.pbxproj
Structure observations
ResetGestureRecognizeris the reusable recognition engine;ViewControllerowns visual piece transforms.- Standard gesture actions and the custom recognizer converge on the same controller-owned UI state.
- The controller’s UIKit delegate methods define coexistence and touch-filtering policy.
Overall architecture
flowchart LR
TOUCH["Touch stream"] --> STANDARD["Pan, pinch, rotation recognizers"]
TOUCH --> RESET["ResetGestureRecognizer"]
RESET --> STATE["possible / began / changed / ended / failed"]
STANDARD --> VC["ViewController actions"]
STATE --> VC
VC --> PIECES["Piece position, scale, rotation, reset"]
Reference code
GestureRecognizer/ViewController.swift:16 — custom recognizer attachment
override func viewDidLoad() {
super.viewDidLoad()
initializePieces()
let resetGestureRecognizer = ResetGestureRecognizer(
target: self,
action: #selector(resetPieces(_:)))
view.addGestureRecognizer(resetGestureRecognizer)
resetGestureRecognizer.delegate = self
resetGestureRecognizer.cancelsTouchesInView = false
}Interpretation
Recognition and presentation are separate. The recognizer reduces raw touches into UIKit states; the controller receives target-action callbacks and applies view transforms. Its delegate conformance decides which recognizers may run together and which touches the reset recognizer receives.
Ownership and state
classDiagram
UIView *-- ResetGestureRecognizer : attached recognizer
ViewController --> ResetGestureRecognizer : target and delegate
ResetGestureRecognizer *-- UITouch : trackedTouch
ResetGestureRecognizer *-- CGPointArray : touchedPoints
ViewController o-- UILabel : storyboard pieces
Ownership evidence
GestureRecognizer/ResetGestureRecognizer.swift:12 — recognizer-local state
class ResetGestureRecognizer: UIGestureRecognizer {
private var trackedTouch: UITouch?
private var touchedPoints = [CGPoint]()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
}
}| Owner | Object or state | Relationship | Mutation authority |
|---|---|---|---|
Root UIView |
ResetGestureRecognizer |
Retains the recognizer after addGestureRecognizer |
UIKit drives its touch callbacks and attachment lifetime. |
ResetGestureRecognizer |
Tracked touch and point history | Owns private recognition state | Only the recognizer resets/appends/interprets it. |
ViewController |
Yellow, blue, and pink piece outlets | Receives storyboard-owned weak references | The controller mutates transforms, centers, bounds, and z-order. |
ViewController |
Recognizer policy | Acts as target and UIKit delegate | It allows simultaneous recognition and filters reset touches. |
Composition denotes source-visible construction and retained value state. Aggregation denotes a stored or injected collaborator whose exclusive lifetime the source does not prove.
Class and protocol design
GestureRecognizer/ResetGestureRecognizer.swift:59 — recognizer state transition
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
let count = countHorizontalTurning(touchedPoints: touchedPoints)
state = count > 2 ? .ended : .failed
}
override func reset() {
super.reset()
trackedTouch = nil
touchedPoints.removeAll()
state = .possible
}| Type | Responsibility | Depends on or conforms to |
|---|---|---|
ResetGestureRecognizer |
Recognizes a path with at least three horizontal turns | UIGestureRecognizer |
ViewController |
Coordinates piece transforms and reset animation | UIViewController, UIGestureRecognizerDelegate |
AppDelegate / SceneDelegate |
Application and scene lifecycle only | UIKit lifecycle protocols |
UIGestureRecognizerDelegate is a framework callback seam. There is no local protocol abstraction; reuse comes from the custom recognizer subclass.
Access control
| Symbol | Access | Verified effect | Design reason |
|---|---|---|---|
trackedTouch, touchedPoints |
private |
Only ResetGestureRecognizer can access recognition history |
Protects the state-machine invariant from controller/UI mutation. |
countHorizontalTurning |
private |
Only the recognizer invokes its detection helper | The algorithm is an implementation detail, not a controller capability. |
initializePieces, adjustAnchor |
private |
Only ViewController uses these UI helpers |
Keeps transform setup behind public/IBAction event entry points. |
| Recognizer and controller types | implicit internal |
Visible inside the app target | The sample exports no reusable framework API. |
Reference code
GestureRecognizer/ResetGestureRecognizer.swift:14 — private recognition state
private var trackedTouch: UITouch?
private var touchedPoints = [CGPoint]()
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
guard state != .failed else { return }
}Declarations without an explicit modifier are module-internal. private is used for state and helpers that implement one type’s invariant; no public library surface is inferred for a single app target.
Logic ownership and placement
| Logic | Owning type or file | Why it lives there |
|---|---|---|
| Horizontal-turn detection and state transitions | ResetGestureRecognizer |
Recognition depends only on touch history and UIKit state. |
| Piece layout and transforms | ViewController |
The controller owns all three visual outlets. |
| Simultaneous/touch filtering policy | ViewController delegate methods |
Policy depends on the screen’s pieces and root view. |
| Lifecycle | AppDelegate and SceneDelegate |
No feature logic is placed in app/scene callbacks. |
Design patterns
| Pattern | Source evidence | Purpose or tradeoff |
|---|---|---|
| Gesture recognizer state machine | GestureRecognizer/ResetGestureRecognizer.swift:23 |
Converts an incremental touch stream into standard UIKit recognizer states. |
| Target-action coordination | GestureRecognizer/ViewController.swift:23 |
The controller reacts to recognition without owning the recognizer algorithm. |
| Framework delegate policy | GestureRecognizer/ViewController.swift:147 |
Screen-specific coexistence/filtering remains outside the reusable recognizer. |
Naming conventions
ResetGestureRecognizernames the user intent and UIKit role, not its horizontal-turn implementation.- Action methods use gesture verbs (
panPiece,pinchPiece,rotatePiece,resetPieces). - Boolean/state names follow UIKit vocabulary (
cancelsTouchesInView, recognizerstate). - The custom recognizer has its own file; lifecycle types remain separate.
Architecture takeaways
- Put raw-touch accumulation and recognition transitions inside the recognizer subclass.
- Keep view transforms and gesture coexistence policy on the screen controller.
- Private recognition state prevents callers from forcing inconsistent transitions.
Source map
| Source file | Architectural role |
|---|---|
GestureRecognizer/ResetGestureRecognizer.swift |
Custom recognition algorithm and state machine |
GestureRecognizer/ViewController.swift |
Recognizer composition, target actions, and delegate policy |
GestureRecognizer/AppDelegate.swift / SceneDelegate.swift |
Application and scene lifecycle |