Search code examples
iosswiftuiviewuikituigesturerecognizer

Simultaneously recognising UIPanGestureRecognizer, UIRotationGestureRecognizer, and UIPinchGestureRecognizer


I have the following code to simultaneously recognise pan, pinch, and rotate gesture recognizers. Problem is pan gesture does not work properly when the view is rotated. On horizontally dragging a rotated view, it also keeps moving up. Here is my code.

import UIKit

 class ViewController: UIViewController, UIGestureRecognizerDelegate {

     private var transformEditingView:UIView!

    override func viewDidLoad() {
         super.viewDidLoad()
        // Do any additional setup after loading the view.
    
         self.view.backgroundColor = UIColor.blue
    
        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(transformViewGestureHandler(_:)))
        panGesture.delegate = self
        panGesture.maximumNumberOfTouches = 1
        self.view.addGestureRecognizer(panGesture)
    
        let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(transformViewGestureHandler(_:)))
        pinchGesture.delegate = self
        self.view.addGestureRecognizer(pinchGesture)
    
        let rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(transformViewGestureHandler(_:)))
        rotationGesture.delegate = self
        self.view.addGestureRecognizer(rotationGesture)
    
        transformEditingView = UIView(frame: self.view.bounds.insetBy(dx: 100, dy: 100))
        transformEditingView?.backgroundColor = UIColor.clear
    
        self.view.addSubview(transformEditingView!)
    
        transformEditingView?.layer.cornerRadius = 4.0
        transformEditingView?.layer.borderWidth = 4.0
        transformEditingView?.layer.borderColor = UIColor.red.cgColor
     }

// MARK: - Gestures for tansformEditingView

func transformUsingRecognizer(_ recognizer: UIGestureRecognizer, transform: CGAffineTransform) -> CGAffineTransform {
    
    if let rotateRecognizer = recognizer as? UIRotationGestureRecognizer {
        return transform.rotated(by: rotateRecognizer.rotation)
    }
    
    if let pinchRecognizer = recognizer as? UIPinchGestureRecognizer {
        let scale = pinchRecognizer.scale
        return transform.scaledBy(x: scale, y: scale)
    }
    
    if let panRecognizer = recognizer as? UIPanGestureRecognizer {
        let deltaX = panRecognizer.translation(in: self.view).x
        let deltaY = panRecognizer.translation(in: self.view).y
        
        return transform.translatedBy(x: deltaX, y: deltaY)
    }
    
    return transform
}

var initialTransform: CGAffineTransform?

var gestures = Set<UIGestureRecognizer>(minimumCapacity: 3)

@IBAction func transformViewGestureHandler(_ gesture: UIGestureRecognizer) {
    
    switch gesture.state {
        
    case .began:
        if gestures.count == 0 {
            initialTransform = transformEditingView.transform
        }
        gestures.insert(gesture)
        
    case .changed:
        if var initialTransform = initialTransform  {
            gestures.forEach({ (gesture) in
                initialTransform = transformUsingRecognizer(gesture, transform: initialTransform)
            })
            transformEditingView.transform = initialTransform
            
        }
        
    case .ended:
        gestures.remove(gesture)
        
    default:
        break
    }
}

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    
    return true
}
  }

   

EDIT: I found that the following change fixes the issue, but not sure why.

    let deltaX = panRecognizer.translation(in: transformEditingView).x
    let deltaY = panRecognizer.translation(in: transformEditingView).y

Solution

  • Give this a try -- seems to work well for simultaneous Pan / Rotate / Scale:

    class PinchPanRotateViewController: UIViewController, UIGestureRecognizerDelegate {
        
        let testView: UILabel = {
            let v = UILabel()
            v.text = "TEST"
            v.textAlignment = .center
            v.textColor = .yellow
            
            v.layer.cornerRadius = 4.0
            v.layer.borderWidth = 4.0
            v.layer.borderColor = UIColor.red.cgColor
            v.layer.masksToBounds = true
    
            //Enable multiple touch and user interaction
            v.isUserInteractionEnabled = true
            v.isMultipleTouchEnabled = true
            
            return v
        }()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            view.backgroundColor = .blue
            
            view.addSubview(testView)
            testView.frame = CGRect(x: 0, y: 0, width: 240, height: 180)
            testView.center = view.center
            
            //add pan gesture
            let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
            gestureRecognizer.delegate = self
            testView.addGestureRecognizer(gestureRecognizer)
            
            //add pinch gesture
            let pinchGesture = UIPinchGestureRecognizer(target: self, action:#selector(handlePinch(_:)))
            pinchGesture.delegate = self
            testView.addGestureRecognizer(pinchGesture)
            
            //add rotate gesture.
            let rotate = UIRotationGestureRecognizer.init(target: self, action: #selector(handleRotate(_:)))
            rotate.delegate = self
            testView.addGestureRecognizer(rotate)
        }
        
        @objc func handlePan(_ pan: UIPanGestureRecognizer) {
            if pan.state == .began || pan.state == .changed {
                guard let v = pan.view else { return }
                let translation = pan.translation(in: self.view)
                v.center = CGPoint(x: v.center.x + translation.x, y: v.center.y + translation.y)
                pan.setTranslation(CGPoint.zero, in: self.view)
            }
        }
        
        @objc func handlePinch(_ pinch: UIPinchGestureRecognizer) {
            guard let v = pinch.view else { return }
            v.transform = v.transform.scaledBy(x: pinch.scale, y: pinch.scale)
            pinch.scale = 1
        }
        
        @objc func handleRotate(_ rotate: UIRotationGestureRecognizer) {
            guard let v = rotate.view else { return }
            v.transform = v.transform.rotated(by: rotate.rotation)
            rotate.rotation = 0
        }
        
        func gestureRecognizer(_: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool {
            return true
        }
    
    }