How to implement multiple PanGestures (Draggable v

2020-05-08 17:23发布

问题:

I want to have several objects that I can drag and drop.

Here's my code for moving one object (with lot of help from @vacawama):

import UIKit

class ViewController: UIViewController {

@IBOutlet weak var panView: UIView!

@IBOutlet weak var panViewCenterX: NSLayoutConstraint!
@IBOutlet weak var panViewCenterY: NSLayoutConstraint!

let panRec = UIPanGestureRecognizer()

override func viewDidLoad() {
    super.viewDidLoad()

    panRec.addTarget(self, action: "draggedView:")
    panView.addGestureRecognizer(panRec)

// Do any additional setup after loading the view, typically from a nib.
}

func draggedView(sender:UIPanGestureRecognizer){

    self.view.bringSubviewToFront(sender.view!)
    var translation = sender.translationInView(self.view)

    panViewCenterY.constant += translation.y
    panViewCenterX.constant += translation.x

    sender.setTranslation(CGPointZero, inView: self.view)
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}   

}

I want to have more objects in my project:

 @IBOutlet weak var panView2: UIView!
 @IBOutlet weak var panView3: UIView!
 @IBOutlet weak var panView4: UIView!
 ...

So, what is the easiest way to implement this in such way that I can have multiple panView which I can move independently around (one at the time)?

回答1:

If you are using Auto Layout, you shouldn't just move a view by altering its center. Anything that causes Auto Layout to run will move your view back to its original position.

Here is an implementation of a new class called DraggableView that works with Auto Layout to provide views that can be dragged. It creates constraints in code for the width, height, X position, and Y position of the view. It uses its own UIPanGestureRecognizer to allow the view to be dragged.

DraggableView.swift can be dropped into any project that needs draggable views. Take a look at ViewController.swift below to see how easy it is to use.

DraggableView.swift

import UIKit

class DraggableView: UIView {
    let superView: UIView!
    let xPosConstraint:  NSLayoutConstraint!
    let yPosConstraint:  NSLayoutConstraint!
    var constraints: [NSLayoutConstraint] {
        get {
            return [xPosConstraint, yPosConstraint]
        }
    }

    init(width: CGFloat, height: CGFloat, x: CGFloat, y: CGFloat, color: UIColor, superView: UIView) {
        super.init()

        self.superView = superView

        self.backgroundColor = color

        self.setTranslatesAutoresizingMaskIntoConstraints(false)

        let panGestureRecognizer = UIPanGestureRecognizer()
        panGestureRecognizer.addTarget(self, action: "draggedView:")
        self.addGestureRecognizer(panGestureRecognizer)

        let widthConstraint = NSLayoutConstraint(item: self, attribute: .Width, relatedBy: .Equal,
            toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: width)
        self.addConstraint(widthConstraint)

        let heightConstraint = NSLayoutConstraint(item: self, attribute: .Height, relatedBy: .Equal,
            toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: height)
        self.addConstraint(heightConstraint)

        xPosConstraint = NSLayoutConstraint(item: self, attribute: .CenterX, relatedBy: .Equal,
            toItem: superView, attribute: .Leading, multiplier: 1.0, constant: x)

        yPosConstraint = NSLayoutConstraint(item: self, attribute: .CenterY, relatedBy: .Equal,
            toItem: superView, attribute: .Top, multiplier: 1.0, constant: y)
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func moveByDeltaX(deltaX: CGFloat, deltaY: CGFloat) {
        xPosConstraint.constant += deltaX
        yPosConstraint.constant += deltaY
    }

    func draggedView(sender:UIPanGestureRecognizer){
        if let dragView = sender.view as? DraggableView {
            superView.bringSubviewToFront(dragView)
            var translation = sender.translationInView(superView)
            sender.setTranslation(CGPointZero, inView: superView)
            dragView.moveByDeltaX(translation.x, deltaY: translation.y)
        }
    }
}

ViewController.swift

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let dragView1 = DraggableView(width: 75, height: 75, x: 50, y: 50,
            color: UIColor.redColor(), superView: self.view)
        self.view.addSubview(dragView1)
        self.view.addConstraints(dragView1.constraints)

        let dragView2 = DraggableView(width: 100, height: 100, x: 150, y: 50,
            color: UIColor.blueColor(), superView: self.view)
        self.view.addSubview(dragView2)
        self.view.addConstraints(dragView2.constraints)

        let dragView3 = DraggableView(width: 125, height: 125, x: 100, y: 175,
            color: UIColor.greenColor(), superView: self.view)
        self.view.addSubview(dragView3)
        self.view.addConstraints(dragView3.constraints)
    }
}

UPDATE:

Here is a version of DraggableView.swift that supports images as a subview.

import UIKit

class DraggableView: UIView {
    let superView: UIView!
    let xPosConstraint:  NSLayoutConstraint!
    let yPosConstraint:  NSLayoutConstraint!
    var constraints: [NSLayoutConstraint] {
        get {
            return [xPosConstraint, yPosConstraint]
        }
    }

    init(width: CGFloat, height: CGFloat, x: CGFloat, y: CGFloat, color: UIColor, superView: UIView, imageToUse: String? = nil, contentMode: UIViewContentMode = .ScaleAspectFill) {
        super.init()

        self.superView = superView

        self.backgroundColor = color

        self.setTranslatesAutoresizingMaskIntoConstraints(false)

        let panGestureRecognizer = UIPanGestureRecognizer()
        panGestureRecognizer.addTarget(self, action: "draggedView:")
        self.addGestureRecognizer(panGestureRecognizer)

        let widthConstraint = NSLayoutConstraint(item: self, attribute: .Width, relatedBy: .Equal,
            toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: width)
        self.addConstraint(widthConstraint)

        let heightConstraint = NSLayoutConstraint(item: self, attribute: .Height, relatedBy: .Equal,
            toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: height)
        self.addConstraint(heightConstraint)

        xPosConstraint = NSLayoutConstraint(item: self, attribute: .CenterX, relatedBy: .Equal,
            toItem: superView, attribute: .Leading, multiplier: 1.0, constant: x)

        yPosConstraint = NSLayoutConstraint(item: self, attribute: .CenterY, relatedBy: .Equal,
            toItem: superView, attribute: .Top, multiplier: 1.0, constant: y)

        if imageToUse != nil {
            if let image = UIImage(named: imageToUse!) {
                let imageView = UIImageView(image: image)
                imageView.contentMode = contentMode
                imageView.clipsToBounds = true
                imageView.setTranslatesAutoresizingMaskIntoConstraints(false)
                self.addSubview(imageView)
                self.addConstraint(NSLayoutConstraint(item: imageView, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1.0, constant: 0))
                self.addConstraint(NSLayoutConstraint(item: imageView, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1.0, constant: 0))
                self.addConstraint(NSLayoutConstraint(item: imageView, attribute: .Right, relatedBy: .Equal, toItem: self, attribute: .Right, multiplier: 1.0, constant: 0))
                self.addConstraint(NSLayoutConstraint(item: imageView, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1.0, constant: 0))
            }
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func moveByDeltaX(deltaX: CGFloat, deltaY: CGFloat) {
        xPosConstraint.constant += deltaX
        yPosConstraint.constant += deltaY
    }

    func draggedView(sender:UIPanGestureRecognizer){
        if let dragView = sender.view as? DraggableView {
            superView.bringSubviewToFront(dragView)
            var translation = sender.translationInView(superView)
            sender.setTranslation(CGPointZero, inView: superView)
            dragView.moveByDeltaX(translation.x, deltaY: translation.y)
        }
    }
}


回答2:

The way a gesture recognizer works is that you attach it to a view. So if you have multiple views, and you want them to be draggable in the same way, attach a different pan gesture recognizer to each one.

Now let's say you want them all to have the same action handler. No problem. In the action handler, you receive the current gesture recognizer as a parameter. It has a view property; that's the view it is attached to! So now you know that that's the view you're moving.



回答3:

Finally:

Found this example which explained it in a easy way: http://www.raywenderlich.com/76020/using-uigesturerecognizer-with-swift-tutorial

So, now multiple Pan's are working..

Here's the code:

import UIKit

class ViewController: UIViewController {


@IBOutlet weak var boxToMove1: UIView!
@IBOutlet weak var boxToMove2: UIView!


override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
}

@IBAction func handlePan(objectToMove:UIPanGestureRecognizer) {

    let translation = objectToMove.translationInView(self.view)
    objectToMove.view!.center = CGPoint(x: objectToMove.view!.center.x + translation.x, y: objectToMove.view!.center.y + translation.y)
    objectToMove.setTranslation(CGPointZero, inView: self.view)

    println("\(objectToMove.view!.tag)") //Verify correct object received
}
override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}
}

I linked up the "Pan Gesture Recognizer" from the "Object library" to each boxToMove. Then I linked the "Pan Gesture Recognizer" (both) to my @IBAction func handlePan...

This is now working!!! Finally I understand the way this Gesture Recognizer is working!

What I'm still trying to figure out is how to do all this linking programmatically.. Any idea?