Search code examples
iosswiftiboutletcollection

How to drag images


I can move a single UIView using the code below but how do I move multiple UIView's individually using IBOutletCollection and tag values?

class TeamSelection: UIViewController {

    var location = CGPoint(x: 0, y: 0)

    @IBOutlet weak var ViewTest: UIView! // move a single image
    @IBOutlet var Player: [UIView]! // collection to enable different images with only one outlet

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {

        let touch: UITouch = touches.first! as UITouch
        location = touch.location(in: self.view)
        ViewTest.center = location

    }

}

Solution

  • There are two basic approaches:

    1. You could iterate through your subviews figuring out in which one the touch intersected and move it. But this approach (nor the use of cryptic tag numeric values to identify the view) is generally not the preferred method.

    2. Personally, I'd put the drag logic in the subview itself:

      class CustomView: UIView {      // or subclass `UIImageView`, as needed
      
          private var originalCenter: CGPoint?
          private var dragStart: CGPoint?
      
          override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
              originalCenter = center
              dragStart = touches.first!.location(in: superview)
          }
      
          override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
              guard let touch = touches.first else { return }
              var location = touch.location(in: superview)
              if let predicted = event?.predictedTouches(for: touch)?.last {
                  location = predicted.location(in: superview)
              }
              center = dragStart! + location - originalCenter!
          }
      
          override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
              guard let touch = touches.first else { return }
              let location = touch.location(in: superview)
              center = dragStart! + location - originalCenter!
          }
      }
      
      extension CGPoint {
          static func +(lhs: CGPoint, rhs: CGPoint) -> CGPoint {
              return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
          }
          static func -(lhs: CGPoint, rhs: CGPoint) -> CGPoint {
              return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
          }
      }
      

      Remember to set "user interaction enabled" for the subviews if you use this approach.

    3. By the way, if you're dragging views like this, make sure you don't have constraints on those views or else when the auto-layout engine next applies itself, everything will move back to the original location. If using auto-layout, you'd generally modify the constant of the constraint.


    A couple of observations on that dragging logic:

    • You might want to use predictive touches, like above, to reduce lagginess of the drag.

    • Rather than moving the center to the location(in:) of the touch, I would rather keep track of by how much I dragged it and either move the center accordingly or apply a corresponding translation. It's a nicer UX, IMHO, because if you grab the the corner, it lets you drag by the corner rather than having it jump the center of the view to where the touch was on screen.