Search code examples
iosswifttransformdraggable

Drag behaviour wrong after UIView transformation


I have a custom DraggableView that subclasses UIImageView. I take a photo with the camera, add the resulting UIImage to a DraggableView and then I can happily drag it around the screen, as intended.

Now, if the original photo was taken in landscape, I rotate it using:

if (image?.size.width > image?.size.height)
{
   self.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_2))
}

When I apply this transformation, the drag behaviour still works, but the directions are all wrong - if I drag left, the image moves up, not left. If I drag up, the image moves right, not up.

How do I fix this? I guess it is something to do with the UIPanGestureRecognizer being bound to the non-transformed view?

Edit: Current UIPanGestureRecognizer handler:

func onPhotoDrag(recognizer: UIPanGestureRecognizer?)
{
    let translation = recognizer!.translationInView(recognizer?.view)
    recognizer!.view!.center = CGPointMake(recognizer!.view!.center.x 
            + translation.x, recognizer!.view!.center.y + translation.y);
    recognizer?.setTranslation(CGPointZero, inView: recognizer?.view)

    if (recognizer!.state == UIGestureRecognizerState.Ended)
    {
        let velocity = recognizer!.velocityInView(recognizer?.view)
        let magnitude = sqrt((velocity.x * velocity.x) 
                + (velocity.y * velocity.y))
        let slideMult = magnitude / 300;
        let slideFactor = 0.1 * slideMult;
        let finalPoint = CGPointMake(recognizer!.view!.center.x 
                + (velocity.x * slideFactor), 
                recognizer!.view!.center.y + (velocity.y * slideFactor));

        // Animate the drag, and allow the drag delegate to do its work
        DraggableView.animateWithDuration(Double(slideFactor), 
                delay: 0, options: UIViewAnimationOptions.CurveEaseOut,
                animations: { recognizer?.view?.center = finalPoint },
                completion: {(_) -> Void in self.dragDelegate?.onDragEnd(self)})
    } // if: gesture ended
}

Solution

  • Update:

    Thanks for posting your code. I pasted your code into my DraggableImageView and reproduced your problem. Although my version was handling the rotated view (without the animation), yours was going sideways.

    The difference is that my code asks for the translationInView in the superview of the draggable view. You need to ask for the translationInView and velocityInView in the superview of your draggable view.

    Change this line:

    let translation = recognizer!.translationInView(recognizer?.view)
    

    to:

    let translation = recognizer!.translationInView(recognizer?.view?.superview)
    

    and change this:

    let velocity = recognizer!.velocityInView(recognizer?.view)
    

    to:

    let velocity = recognizer!.velocityInView(recognizer?.view?.superview)
    

    and all will be happy.


    Previous Answer:

    Try this version:

    class DraggableImageView: UIImageView {
        
        override var image: UIImage? {
            didSet {
                if (image?.size.width > image?.size.height)
                {
                    self.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_2))
                }
            }
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            
            self.setup()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            
            self.setup()
        }
        
        func setup() {
            self.userInteractionEnabled = true
            
            let panGestureRecognizer = UIPanGestureRecognizer()
            panGestureRecognizer.addTarget(self, action: #selector(draggedView(_:)))
            self.addGestureRecognizer(panGestureRecognizer)
        }
        
        func moveByDeltaX(deltaX: CGFloat, deltaY: CGFloat) {
            self.center.x += deltaX
            self.center.y += deltaY
        }
        
        func draggedView(sender:UIPanGestureRecognizer) {
            if let dragView = sender.view as? DraggableImageView, superview = dragView.superview {
                superview.bringSubviewToFront(dragView)
                let translation = sender.translationInView(superview)
                sender.setTranslation(CGPointZero, inView: superview)
                dragView.moveByDeltaX(translation.x, deltaY: translation.y)
            }
        }
    }
    

    Use example:

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let dragView = DraggableImageView(frame: CGRect(x: 50, y: 50, width: 96, height: 128))
        
        dragView.image = UIImage(named: "landscapeImage.png")
        
        self.view.addSubview(dragView)
    }