┌─────────────────────────────┐ │ │ │ [ ■ ] │ │ │ │ Drag, pinch, or rotate the │ │ square above with your │ │ fingers. │ │ │ └─────────────────────────────┘
Gesture recognition (drag, magnify, rotate) in iOS Swift - Mini App: Build & Ship
import UIKit class GestureDemoViewController: UIViewController { let squareView = UIView() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white squareView.backgroundColor = .systemBlue squareView.frame = CGRect(x: 0, y: 0, width: 150, height: 150) squareView.center = view.center view.addSubview(squareView) // TODO: Add gesture recognizers here } }
import UIKit class GestureDemoViewController: UIViewController, UIGestureRecognizerDelegate { let squareView = UIView() var currentTransform = CGAffineTransform.identity override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white squareView.backgroundColor = .systemBlue squareView.frame = CGRect(x: 0, y: 0, width: 150, height: 150) squareView.center = view.center view.addSubview(squareView) let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))) panGesture.delegate = self squareView.addGestureRecognizer(panGesture) let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(_:))) pinchGesture.delegate = self squareView.addGestureRecognizer(pinchGesture) let rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(handleRotation(_:))) rotationGesture.delegate = self squareView.addGestureRecognizer(rotationGesture) squareView.isUserInteractionEnabled = true } @objc func handlePan(_ gesture: UIPanGestureRecognizer) { let translation = gesture.translation(in: view) if gesture.state == .began || gesture.state == .changed { let translatedTransform = currentTransform.translatedBy(x: translation.x, y: translation.y) squareView.transform = translatedTransform } else if gesture.state == .ended || gesture.state == .cancelled { currentTransform = squareView.transform keepSquareInsideBounds() } } @objc func handlePinch(_ gesture: UIPinchGestureRecognizer) { if gesture.state == .began || gesture.state == .changed { let scaledTransform = currentTransform.scaledBy(x: gesture.scale, y: gesture.scale) squareView.transform = scaledTransform } else if gesture.state == .ended || gesture.state == .cancelled { currentTransform = squareView.transform keepSquareInsideBounds() } } @objc func handleRotation(_ gesture: UIRotationGestureRecognizer) { if gesture.state == .began || gesture.state == .changed { let rotatedTransform = currentTransform.rotated(by: gesture.rotation) squareView.transform = rotatedTransform } else if gesture.state == .ended || gesture.state == .cancelled { currentTransform = squareView.transform keepSquareInsideBounds() } } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } private func keepSquareInsideBounds() { var frame = squareView.frame let superBounds = view.bounds if frame.minX < superBounds.minX { frame.origin.x = superBounds.minX } if frame.maxX > superBounds.maxX { frame.origin.x = superBounds.maxX - frame.width } if frame.minY < superBounds.minY { frame.origin.y = superBounds.minY } if frame.maxY > superBounds.maxY { frame.origin.y = superBounds.maxY - frame.height } UIView.animate(withDuration: 0.2) { self.squareView.frame = frame } currentTransform = self.squareView.transform } }
We created a blue square view centered on the screen. We added three gesture recognizers: pan for dragging, pinch for zooming, and rotation for rotating the square. Each gesture updates the square's transform property to move, scale, or rotate it.
We keep track of the current transform so gestures build on the previous state. The gesture recognizer delegate allows all gestures to work simultaneously.
After gestures end, we check if the square is still fully visible inside the screen bounds and adjust its position if needed, so it never goes off-screen.
This approach gives a smooth and natural interaction for dragging, zooming, and rotating the square.
┌─────────────────────────────┐ │ │ │ [ ■ ] │ │ │ │ Drag, pinch, or rotate the │ │ square above with your │ │ fingers. │ │ │ └─────────────────────────────┘
Practice
Solution
Step 1: Understand gesture types
DragGesture detects dragging motions, MagnificationGesture detects pinch zoom, RotationGesture detects rotation, and TapGesture detects taps.Step 2: Match gesture to dragging
Since dragging means moving finger across the screen, DragGesture is the correct recognizer.Final Answer:
DragGesture -> Option CQuick Check:
DragGesture = dragging motion [OK]
- Confusing MagnificationGesture with dragging
- Choosing TapGesture for drag
- Mixing RotationGesture with drag
Solution
Step 1: Identify rotation gesture syntax
RotationGesture() is the correct gesture for rotation, and .gesture() modifier adds it to a view.Step 2: Confirm onChanged closure usage
onChanged receives the rotation angle, so the syntax .gesture(RotationGesture().onChanged { angle in ... }) is correct.Final Answer:
.gesture(RotationGesture().onChanged { angle in /* code */ }) -> Option AQuick Check:
RotationGesture syntax = .gesture(RotationGesture().onChanged { angle in /* code */ }) [OK]
- Using DragGesture instead of RotationGesture
- Using .onTapGesture for rotation
- Confusing MagnificationGesture with rotation
Image("photo")
.scaleEffect(scale)
.rotationEffect(angle)
.offset(dragAmount)
.gesture(
DragGesture()
.onChanged { dragAmount = $0.translation }
.onEnded { _ in dragAmount = .zero }
)
.gesture(
MagnificationGesture()
.onChanged { scale = $0 }
)
.gesture(
RotationGesture()
.onChanged { angle = $0 }
)Solution
Step 1: Analyze gesture modifiers
The code adds three gestures: DragGesture updates dragAmount offset, MagnificationGesture updates scale, RotationGesture updates angle.Step 2: Understand combined effects
The view applies scaleEffect, rotationEffect, and offset using these state variables, so all gestures affect the image interactively.Final Answer:
The image can be dragged, zoomed, and rotated by user gestures. -> Option BQuick Check:
All gestures update view transforms = The image can be dragged, zoomed, and rotated by user gestures. [OK]
- Assuming only one gesture works at a time
- Ignoring that gestures update state variables
- Thinking gestures override each other
Circle()
.gesture(DragGesture().onChanged { value in
dragAmount = value.translation
})
.gesture(MagnificationGesture().onChanged { scale = $0 })Solution
Step 1: Understand gesture modifiers stacking
Applying multiple .gesture modifiers separately causes only the last one to be active.Step 2: Identify which gesture is active
Since MagnificationGesture is added last, it overrides DragGesture, so only magnification works.Final Answer:
Only the MagnificationGesture will work; DragGesture is ignored. -> Option AQuick Check:
Last .gesture overrides previous = Only the MagnificationGesture will work; DragGesture is ignored. [OK]
- Assuming both gestures work independently
- Expecting compile error for multiple gestures
- Thinking first gesture always wins
Solution
Step 1: Understand gesture combination in SwiftUI
Multiple .gesture modifiers override each other, so to combine gestures, use .simultaneousGesture.Step 2: Apply simultaneousGesture for all three
By nesting .simultaneousGesture for DragGesture, MagnificationGesture, and RotationGesture, all gestures work together smoothly.Final Answer:
Use .simultaneousGesture to combine DragGesture, MagnificationGesture, and RotationGesture on the view. -> Option DQuick Check:
Combine gestures with simultaneousGesture = Use .simultaneousGesture to combine DragGesture, MagnificationGesture, and RotationGesture on the view. [OK]
- Adding multiple .gesture modifiers separately
- Expecting one gesture to handle all types
- Ignoring zoom and rotate gestures
Ready to prove it?
Pass the test and this pattern moves unknown
