How to implement multiple PanGestures (Draggable views)?

北城余情 提交于 2019-12-02 08:44:24

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)
        }
    }
}

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.

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?

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!