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
numberOfTapsRequiredfor double taps (2) or more, andnumberOfTouchesRequiredfor multi-finger taps. - Target-Action: The
targetis the object handling the gesture, andactionis 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.scaleindicates the pinch scale relative to the start of the gesture. - State: Handle
.beganor.changedstates to apply transformations dynamically. - Reset Scale: Set
gesture.scale = 1.0after 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.rotationprovides the rotation angle in radians. - State: Apply transformations in
.beganor.changedstates. - Reset Rotation: Set
gesture.rotation = 0.0after 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
directionto.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
UISwipeGestureRecognizerinstances 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:
allowableMovementdefines how far the finger can move before the gesture fails (default is 10 pixels). - State: Handle
.began,.changed, or.endedstates 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 = selfRequiring Gesture Failure
Make one gesture depend on another failing:
tapGesture.require(toFail: longPressGesture) // Tap only triggers if long press failsBest Practices
- Enable User Interaction: Ensure
view.isUserInteractionEnabled = true. - Minimal Gestures: Use only necessary recognizers to avoid conflicts.
- Delegate: Use
UIGestureRecognizerDelegateto 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.stateto 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.