Gesture Recognizers
Gesture recognizers in UIKit allow developers to detect and respond to user interactions such as taps, pinches, rotations, swipes, and long presses. By attaching UIGestureRecognizer
subclasses to a UIView
, apps can handle touch-based interactions efficiently. This document covers attaching tap, pinch, rotation, swipe, and long press gesture recognizers to a view.
Attaching Tap Gesture Recognizers to a View
A tap gesture recognizer (UITapGestureRecognizer
) detects single or multiple taps on a view.
Implementation
Create and configure a UITapGestureRecognizer
, then add it to a view.
Example
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var sampleView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
tapGesture.numberOfTapsRequired = 1 // Default is 1 (single tap)
tapGesture.numberOfTouchesRequired = 1 // Default is 1
sampleView.addGestureRecognizer(tapGesture)
}
@objc func handleTap(_ gesture: UITapGestureRecognizer) {
print("Tapped at \(gesture.location(in: sampleView))")
}
}
Key Points
- Configuration: Set
numberOfTapsRequired
for double taps (2) or more, andnumberOfTouchesRequired
for multi-finger taps. - Target-Action: The
target
is the object handling the gesture, andaction
is the selector to call. - Location: Use
gesture.location(in:)
to get the tap’s coordinates in the view.
Attaching Pinch Gesture Recognizers to a View
A pinch gesture recognizer (UIPinchGestureRecognizer
) detects two-finger pinch gestures for scaling.
Implementation
Add a UIPinchGestureRecognizer
to a view and handle scaling in the action method.
Example
override func viewDidLoad() {
super.viewDidLoad()
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(_:)))
sampleView.addGestureRecognizer(pinchGesture)
}
@objc func handlePinch(_ gesture: UIPinchGestureRecognizer) {
guard gesture.view != nil else { return }
if gesture.state == .began || gesture.state == .changed {
let scale = gesture.scale
gesture.view?.transform = gesture.view!.transform.scaledBy(x: scale, y: scale)
gesture.scale = 1.0 // Reset scale to avoid compounding
print("Pinched with scale: \(scale)")
}
}
Key Points
- Scale:
gesture.scale
indicates the pinch scale relative to the start of the gesture. - State: Handle
.began
or.changed
states to apply transformations dynamically. - Reset Scale: Set
gesture.scale = 1.0
after applying the transform to prevent cumulative scaling.
Attaching a Rotation Gesture Recognizer to a View
A rotation gesture recognizer (UIRotationGestureRecognizer
) detects two-finger rotation gestures.
Implementation
Add a UIRotationGestureRecognizer
and apply a rotation transform.
Example
override func viewDidLoad() {
super.viewDidLoad()
let rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(handleRotation(_:)))
sampleView.addGestureRecognizer(rotationGesture)
}
@objc func handleRotation(_ gesture: UIRotationGestureRecognizer) {
guard gesture.view != nil else { return }
if gesture.state == .began || gesture.state == .changed {
let rotation = gesture.rotation
gesture.view?.transform = gesture.view!.transform.rotated(by: rotation)
gesture.rotation = 0.0 // Reset rotation to avoid compounding
print("Rotated by: \(rotation) radians")
}
}
Key Points
- Rotation:
gesture.rotation
provides the rotation angle in radians. - State: Apply transformations in
.began
or.changed
states. - Reset Rotation: Set
gesture.rotation = 0.0
after applying the transform to prevent cumulative rotation.
Attaching Swipe Gesture Recognizers to a View
A swipe gesture recognizer (UISwipeGestureRecognizer
) detects swipes in specified directions.
Implementation
Configure a UISwipeGestureRecognizer
with a direction and add it to a view.
Example
override func viewDidLoad() {
super.viewDidLoad()
let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:)))
swipeGesture.direction = .right // Can combine directions: [.right, .left]
sampleView.addGestureRecognizer(swipeGesture)
}
@objc func handleSwipe(_ gesture: UISwipeGestureRecognizer) {
let direction = gesture.direction
print("Swiped \(direction == .right ? "right" : "other")")
}
Key Points
- Direction: Set
direction
to.right
,.left
,.up
, or.down
(or combine with|
). Default is.right
. - Single Event: Swipes are discrete gestures, triggering only when completed.
- Multiple Recognizers: Use multiple
UISwipeGestureRecognizer
instances for different directions.
Attaching a Long Press Gesture Recognizer to a View
A long press gesture recognizer (UILongPressGestureRecognizer
) detects presses held for a specified duration.
Implementation
Add a UILongPressGestureRecognizer
and configure properties like duration or allowable movement.
Example
override func viewDidLoad() {
super.viewDidLoad()
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
longPressGesture.minimumPressDuration = 0.5 // Seconds
longPressGesture.allowableMovement = 10 // Pixels
sampleView.addGestureRecognizer(longPressGesture)
}
@objc func handleLongPress(_ gesture: UILongPressGestureRecognizer) {
if gesture.state == .began {
print("Long press began at \(gesture.location(in: sampleView))")
} else if gesture.state == .ended {
print("Long press ended")
}
}
Key Points
- Duration: Set
minimumPressDuration
(default is 0.5 seconds). - Movement:
allowableMovement
defines how far the finger can move before the gesture fails (default is 10 pixels). - State: Handle
.began
,.changed
, or.ended
states for continuous feedback.
Gesture Recognizer Management
Simultaneous Recognition
Allow multiple gestures to work together:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true // Enable simultaneous pinch and rotation, for example
}
Set the delegate and implement UIGestureRecognizerDelegate
:
pinchGesture.delegate = self
rotationGesture.delegate = self
Requiring Gesture Failure
Make one gesture depend on another failing:
tapGesture.require(toFail: longPressGesture) // Tap only triggers if long press fails
Best Practices
- Enable User Interaction: Ensure
view.isUserInteractionEnabled = true
. - Minimal Gestures: Use only necessary recognizers to avoid conflicts.
- Delegate: Use
UIGestureRecognizerDelegate
to manage conflicts or simultaneous gestures. - Accessibility: Ensure gestures are optional or provide alternative controls for VoiceOver users.
- Testing: Test on various devices (iPhone, iPad) and input methods (touch, trackpad).
- State Handling: Check
gesture.state
to respond appropriately to gesture phases.
Limitations
- Conflicts: Multiple gestures on the same view can conflict; use delegate methods or
require(toFail:)
to resolve. - Precision: Some gestures (e.g., pinch, rotation) require multi-touch, which may not work with all input devices.
- iOS Only: Gesture recognizers are UIKit-specific and unavailable in macOS without Mac Catalyst.
Summary
UIKit gesture recognizers (UITapGestureRecognizer
, UIPinchGestureRecognizer
, UIRotationGestureRecognizer
, UISwipeGestureRecognizer
, UILongPressGestureRecognizer
) enable rich touch interactions in iOS apps. By configuring properties, handling states, and managing conflicts, developers can create intuitive and responsive user experiences tailored to specific interactions.