Bird
Raised Fist0
iOS Swiftmobile~20 mins

Gesture recognition (drag, magnify, rotate) in iOS Swift - Mini App: Build & Ship

Choose your learning style10 modes available
Build: Gesture Demo Screen
This screen lets the user drag, pinch to zoom, and rotate a square view using gestures.
Target UI
┌─────────────────────────────┐
│                             │
│          [  ■  ]            │
│                             │
│  Drag, pinch, or rotate the  │
│  square above with your      │
│  fingers.                    │
│                             │
└─────────────────────────────┘
Add a square UIView centered on the screen with a distinct background color.
Enable dragging the square around the screen with a pan gesture.
Enable pinch gesture to magnify (zoom) the square.
Enable rotation gesture to rotate the square.
All gestures should work smoothly and simultaneously.
The square should stay visible within the screen bounds.
Starter Code
iOS Swift
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
    }
}
Task 1
Task 2
Task 3
Task 4
Task 5
Solution
iOS Swift
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.

Final Result
Completed Screen
┌─────────────────────────────┐
│                             │
│          [  ■  ]            │
│                             │
│  Drag, pinch, or rotate the  │
│  square above with your      │
│  fingers.                    │
│                             │
└─────────────────────────────┘
User drags the blue square around the screen with one finger.
User pinches with two fingers to zoom the square bigger or smaller.
User rotates with two fingers to spin the square.
All gestures can be combined smoothly.
The square stays fully visible inside the screen edges.
Stretch Goal
Add a reset button that returns the square to its original size, rotation, and center position.
💡 Hint
Add a UIButton below the square. On tap, set squareView.transform to identity and center it again.

Practice

(1/5)
1. Which gesture recognizer in SwiftUI detects a dragging motion on a view?
easy
A. MagnificationGesture
B. RotationGesture
C. DragGesture
D. TapGesture

Solution

  1. Step 1: Understand gesture types

    DragGesture detects dragging motions, MagnificationGesture detects pinch zoom, RotationGesture detects rotation, and TapGesture detects taps.
  2. Step 2: Match gesture to dragging

    Since dragging means moving finger across the screen, DragGesture is the correct recognizer.
  3. Final Answer:

    DragGesture -> Option C
  4. Quick Check:

    DragGesture = dragging motion [OK]
Hint: DragGesture is for dragging, Magnification for pinch zoom [OK]
Common Mistakes:
  • Confusing MagnificationGesture with dragging
  • Choosing TapGesture for drag
  • Mixing RotationGesture with drag
2. Which of the following is the correct syntax to add a rotation gesture recognizer to a SwiftUI view?
easy
A. .gesture(RotationGesture().onChanged { angle in /* code */ })
B. .onTapGesture { /* code */ }
C. .gesture(DragGesture().onEnded { _ in /* code */ })
D. .gesture(MagnificationGesture().onChanged { scale in /* code */ })

Solution

  1. Step 1: Identify rotation gesture syntax

    RotationGesture() is the correct gesture for rotation, and .gesture() modifier adds it to a view.
  2. Step 2: Confirm onChanged closure usage

    onChanged receives the rotation angle, so the syntax .gesture(RotationGesture().onChanged { angle in ... }) is correct.
  3. Final Answer:

    .gesture(RotationGesture().onChanged { angle in /* code */ }) -> Option A
  4. Quick Check:

    RotationGesture syntax = .gesture(RotationGesture().onChanged { angle in /* code */ }) [OK]
Hint: Use .gesture(RotationGesture().onChanged) for rotation [OK]
Common Mistakes:
  • Using DragGesture instead of RotationGesture
  • Using .onTapGesture for rotation
  • Confusing MagnificationGesture with rotation
3. What will be the effect of this SwiftUI code snippet on a view?
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 }
  )
medium
A. Only dragging works; zoom and rotation gestures are ignored.
B. The image can be dragged, zoomed, and rotated by user gestures.
C. Only zooming works; drag and rotation gestures are ignored.
D. The image is static and does not respond to gestures.

Solution

  1. Step 1: Analyze gesture modifiers

    The code adds three gestures: DragGesture updates dragAmount offset, MagnificationGesture updates scale, RotationGesture updates angle.
  2. Step 2: Understand combined effects

    The view applies scaleEffect, rotationEffect, and offset using these state variables, so all gestures affect the image interactively.
  3. Final Answer:

    The image can be dragged, zoomed, and rotated by user gestures. -> Option B
  4. Quick Check:

    All gestures update view transforms = The image can be dragged, zoomed, and rotated by user gestures. [OK]
Hint: Multiple gestures update scale, rotation, and offset simultaneously [OK]
Common Mistakes:
  • Assuming only one gesture works at a time
  • Ignoring that gestures update state variables
  • Thinking gestures override each other
4. Identify the error in this SwiftUI gesture code snippet:
Circle()
  .gesture(DragGesture().onChanged { value in
    dragAmount = value.translation
  })
  .gesture(MagnificationGesture().onChanged { scale = $0 })
medium
A. Only the MagnificationGesture will work; DragGesture is ignored.
B. Both gestures work correctly without conflict.
C. The code will cause a compile-time error due to multiple gestures.
D. DragGesture will override MagnificationGesture, so only drag works.

Solution

  1. Step 1: Understand gesture modifiers stacking

    Applying multiple .gesture modifiers separately causes only the last one to be active.
  2. Step 2: Identify which gesture is active

    Since MagnificationGesture is added last, it overrides DragGesture, so only magnification works.
  3. Final Answer:

    Only the MagnificationGesture will work; DragGesture is ignored. -> Option A
  4. Quick Check:

    Last .gesture overrides previous = Only the MagnificationGesture will work; DragGesture is ignored. [OK]
Hint: Multiple .gesture modifiers override; combine gestures with simultaneousGesture [OK]
Common Mistakes:
  • Assuming both gestures work independently
  • Expecting compile error for multiple gestures
  • Thinking first gesture always wins
5. You want to allow a user to drag, zoom, and rotate a photo simultaneously in SwiftUI. Which approach correctly combines these gestures so all work together smoothly?
hard
A. Use only one gesture recognizer that detects drag, zoom, and rotate at once.
B. Add three separate .gesture modifiers for each gesture on the view.
C. Use .gesture for DragGesture and ignore zoom and rotate gestures.
D. Use .simultaneousGesture to combine DragGesture, MagnificationGesture, and RotationGesture on the view.

Solution

  1. Step 1: Understand gesture combination in SwiftUI

    Multiple .gesture modifiers override each other, so to combine gestures, use .simultaneousGesture.
  2. Step 2: Apply simultaneousGesture for all three

    By nesting .simultaneousGesture for DragGesture, MagnificationGesture, and RotationGesture, all gestures work together smoothly.
  3. Final Answer:

    Use .simultaneousGesture to combine DragGesture, MagnificationGesture, and RotationGesture on the view. -> Option D
  4. Quick Check:

    Combine gestures with simultaneousGesture = Use .simultaneousGesture to combine DragGesture, MagnificationGesture, and RotationGesture on the view. [OK]
Hint: Combine gestures with .simultaneousGesture for multi-touch support [OK]
Common Mistakes:
  • Adding multiple .gesture modifiers separately
  • Expecting one gesture to handle all types
  • Ignoring zoom and rotate gestures
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard