如何实现多个 PanGestures(可拖动视图)?

Posted

技术标签:

【中文标题】如何实现多个 PanGestures(可拖动视图)?【英文标题】:How to implement multiple PanGestures (Draggable views)? 【发布时间】:2014-11-25 01:02:17 【问题描述】:

我想要几个可以拖放的对象。

这是我移动一个对象的代码(在@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.
   


我想在我的项目中有更多对象:

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

那么,以我可以拥有多个panView 的方式实现这一点的最简单方法是什么,我可以独立移动(一次一个)?

【问题讨论】:

就这么做吧。有什么问题? 我的问题是我不完全理解我实际上在做什么(这就是我在这里发帖的原因)。这是我的“理解”:我检测到带有panRec 的PanGesture 并将该手势传递给func draggedView,然后我在那里设置了新位置。但是panView.addGestureRecognizer(panRec) 到底是做什么的。或者是周围的其他方式?我希望有一种方法可以让我拥有一个 panRec 并使用即 switch 语句测试它是 panView1panView2 还是 panView3 等..然后相应地设置位置。 我经历了这么多例子,啧啧,我很快就要放弃了……用“-1”打我并没有真正的帮助;-) 不要放弃!我对如何以更简单的方式做到这一点有一个想法。它涉及一个跟踪其自身约束的 UIView 子类(称为 DraggableView)。它涉及在代码中而不是在 Interface Builder 中创建约束。不过,我在 12 小时内无法解决此问题,因此除非其他人回答,否则您必须耐心等待。 我会在等待的时候继续看下去;-) 【参考方案1】:

如果您使用自动布局,您不应该只是通过改变其中心来移动视图。任何导致 Auto Layout 运行的东西都会将您的视图移回其原始位置。

这是一个名为DraggableView 的新类的实现,它与Auto Layout 一起工作,以提供可以拖动的视图。它在代码中为视图的宽度、高度、X 位置和 Y 位置创建约束。它使用自己的UIPanGestureRecognizer 来允许拖动视图。

DraggableView.swift 可以拖放到任何需要可拖动视图的项目中。看看下面的 ViewController.swift 看看它是多么容易使用。

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)
    


更新:

这是一个支持图像作为子视图的 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, 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)
        
    

【讨论】:

这很聪明,但有点不必要。如果视图将是可拖动的,则它不适合通过自动布局定位。因此,只需将其从自动布局中取出。在使用自动布局的界面中,一个或两个特殊视图不受自动布局的约束是完全合理/合法的。 如何“从自动布局中查看视图”? @matt,这是 OP 之前提出的问题的后续问题。在那一个中​​,他正在更新UILabel,并且由于自动布局,他的拖动视图停止工作。他说他想要自动布局,所以这就是我走这条路的原因。我不知道如何禁用单个视图的自动布局,仅适用于情节提要或 XIB。 我不是在批评!我只是说在很多情况下这可能是矫枉过正。假设我们有一种益智游戏,顶部有一些按钮,中间有一个带有一些可拖动视图的区域。那么可拖动视图不参与自动布局可能是有意义的。有时人们不会想到自动布局和非自动布局视图可以在同一个界面中组合的事实;这就是我指出它的原因。 我不认为你在批评。我不明白如何使可拖动视图不参与自动布局。【参考方案2】:

手势识别器的工作方式是将其附加到视图上。因此,如果您有多个视图,并且希望它们能够以相同的方式拖动,请为每个视图附加不同的平移手势识别器。

现在假设您希望它们都具有相同的操作处理程序。没问题。在动作处理程序中,您接收当前手势识别器作为参数。它有一个view 属性;这就是它所附加的视图!所以现在您知道这就是您要移动的视图。

【讨论】:

但总的来说,您不认为在使用手势识别器之前了解会更好吗,这样您就不会四处奔波并说“我不知道” '不完全理解我实际上在做什么'?例如,我在这里向您解释:apeth.com/iosBook/ch18.html#_gesture_recognizers 是的,总的来说,我同意!我理解手势的概念,这是我正在努力解决的实现部分。我会继续阅读,谢谢链接。【参考方案3】:

最后:

找到了这个例子,它以一种简单的方式解释了它: http://www.raywenderlich.com/76020/using-uigesturerecognizer-with-swift-tutorial

所以,现在多个 Pan 正在工作..

代码如下:

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.


我将“对象库”中的“平移手势识别器”链接到每个boxToMove。然后我将“Pan Gesture Recognizer”(两者)链接到我的@IBAction func handlePan...

现在可以了!!! 终于明白这个手势识别器的工作原理了!

我仍然想弄清楚的是如何以编程方式进行所有这些链接。有什么想法吗?

【讨论】:

以上是关于如何实现多个 PanGestures(可拖动视图)?的主要内容,如果未能解决你的问题,请参考以下文章

使用 pageViews 的可拖动网格视图

Xamarin Forms 如何制作可拖动的列表视图

像 iPhone facebook 应用程序一样的可拖动 ViewController

如何在 Java 中将多个可拖动图像添加到 JFrame?

javascript - 如何制作多个可拖动的克隆?

一、Zbrush基础操作