32

I want to be able to drag the objects on the screen, but they wont. I tried everything but still cant.

Here are the code.

func panGesture(gesture: UIPanGestureRecognizer) {
    switch gesture.state {
    case .began:
        print("Began.")
        for i in 0..<forms.count {
            if forms[i].frame.contains(gesture.location(in: view)) {
                gravity.removeItem(forms[i])
            }
        }
    case .changed:
        let translation = gesture.translation(in: forms[1])

        gesture.view!.center = CGPoint(x: gesture.view!.center.x + translation.x, y: gesture.view!.center.y + translation.y)

        gesture.setTranslation(CGPoint.zero, in: self.view)

        print("\(gesture.view!.center.x)=\(gesture.view!.center.y)")
        print("t;: \(translation)")
    case .ended:
        for i in 0..<forms.count {
            if forms[i].frame.contains(gesture.location(in: view)) {
                gravity.addItem(forms[i])
            }
        }
        print("Ended.")
    case .cancelled:
        print("Cancelled")
    default:
        print("Default")
    }
}

Also they have gravity. The forms are squares and circles.

Explanation: in .began - i disable the gravity for selected form. in .changed - i try to change the coordinates. in .end - i enable again gravity.

ScreenShot.

enter image description here

enter image description here

Xcodian Solangi
  • 2,342
  • 5
  • 24
  • 52
Dpetrov
  • 546
  • 2
  • 5
  • 12

4 Answers4

120

Step 1 : Take one View which you want to drag in storyBoard.

@IBOutlet weak var viewDrag: UIView!

Step 2 : Add PanGesture.

var panGesture       = UIPanGestureRecognizer()

Step 3 : In ViewDidLoad adding the below code.

override func viewDidLoad() {
    super.viewDidLoad()

    panGesture = UIPanGestureRecognizer(target: self, action: #selector(ViewController.draggedView(_:)))
    viewDrag.isUserInteractionEnabled = true
    viewDrag.addGestureRecognizer(panGesture)

}

Step 4 : Code for draggedView.

func draggedView(_ sender:UIPanGestureRecognizer){
    self.view.bringSubview(toFront: viewDrag)
    let translation = sender.translation(in: self.view)
    viewDrag.center = CGPoint(x: viewDrag.center.x + translation.x, y: viewDrag.center.y + translation.y)
    sender.setTranslation(CGPoint.zero, in: self.view)
}

Step 5 : Output.

GIF

Kirit Modi
  • 23,155
  • 15
  • 89
  • 112
  • 1
    Comments are not for extended discussion; this conversation has been [moved to chat](http://chat.stackoverflow.com/rooms/143103/discussion-on-answer-by-kirit-modi-draggable-uiview-swift-3). – Bhargav Rao May 01 '17 at 16:11
  • Inside the draggedView I used sender.view instead so that I could reuse the draggedView function for other draggable views. – Neo42 Aug 31 '17 at 17:29
  • Firstly, Thanks a lot for the simple solution. Its working pretty neat in my app where i am making use of this implementation for Secondary Camera view ( my own video within Video calling screen ). But the problem I am facing is when the timer is started and its refreshing the timer label every seconds. As soon as timer label starts, the view gets back to the original place and later if I drag the view it gets back to original place. – i70ro Mar 14 '18 at 08:01
  • 2
    We need to add @obj to draggedView for swift 4 – touti Sep 11 '18 at 12:03
  • How to stop the view from getting dragged out of the screen? When I drag to left near the edge of screen and still drag the view goes out of bounds, how to prevent that? – Arjun Jan 19 '22 at 05:55
7

It's very easy if you subclass a view:

DraggableView...

class DraggableView: UIIView {
    
    var fromleft: NSLayoutConstraint!
    var fromtop: NSLayoutConstraint!
    
    override func didMoveToWindow() {
        super.didMoveToWindow()
        if window != nil {
            fromleft = constraint(id: "fromleft")!
            fromtop = constraint(id: "fromtop")!
        }
    }
    
    override func common() {
        super.common()
        let p = UIPanGestureRecognizer(
           target: self, action: #selector(drag))
        addGestureRecognizer(p)
    }
    
    @objc func drag(_ s:UIPanGestureRecognizer) {
        let t = s.translation(in: self.superview)
        fromleft.constant = fromleft.constant + t.x
        fromtop.constant = fromtop.constant + t.y
        s.setTranslation(CGPoint.zero, in: self.superview)
    }
}
  1. Drop a UIView in your scene.

  2. As normal, add a constraint from the left (that's the x position) and add a constraint from the top (that's the y position).

  3. In storyboard simply simply name the constraints "fromleft" and "fromtop"

enter image description here

You're done.

It now works perfectly - that's it.

What is that handy constraint( call ?

Notice the view simply finds its own constraints by name.

In Xcode there is STILL no way to use constraints like IBOutlets. Fortunately it is very easy to find them by "identifier". (Indeed, this is the very purpose of the .identifier feature on constraints.)

extension UIView {
    
    func constraint(id: String) -> NSLayoutConstraint? {
        let cc = self.allConstraints()
        for c in cc { if c.identifier == id { return c } }
        //print("someone forgot to label constraint \(id)") //heh!
        return nil
    }
    
    func allConstraints() -> [NSLayoutConstraint] {
        var views = [self]
        var view = self
        while let superview = view.superview {
            views.append(superview)
            view = superview
        }
        return views.flatMap({ $0.constraints }).filter { c in
            return c.firstItem as? UIView == self ||
                c.secondItem as? UIView == self
        }
    }

Tip...edge versus center!

Don't forget when you make the constraints on a view (as in the image above):

you can set the left one to be either:

  • to the left edge of the white box, or,
  • to the center of the white box.

Choose the correct one for your situation. It will make it much easier to do calculations, set sliders, etc.


Footnote - an "initializing" UIView, UI "I" View,

// UI "I" View ... "I" for initializing
// Simply saves you typing inits everywhere
import UIKit
class UIIView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        common()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        common()
    }
    func common() { }
}
Fattie
  • 27,874
  • 70
  • 431
  • 719
  • 1
    This one is perfect, Thank you @Fattie, You are awesome – Mehul Oct 27 '20 at 12:04
  • heh thanks @Mehul ! It is definitely the only way to go these days. – Fattie Oct 27 '20 at 13:45
  • I agree with you. Thank you for your contribution. You save my time & energy. – Mehul Oct 27 '20 at 14:32
  • @Fattie, this is nice. Do you have an equally succinct way of incorporating into this solution an adjustment to the translation to cancel out the ["distance for recognition as a pan"](https://developer.apple.com/documentation/uikit/uipangesturerecognizer) as Apple puts it? – Paul Nov 15 '20 at 00:54
  • 1
    @Paul , funny you should ask. That is a "famous problem" in iOS. And here's the famous answer from some years ago: https://stackoverflow.com/a/19145354/294884 (notice a certain handsome list member added in some recent code :) ) Pls note that every "custom touch need" can take a LOT of work; there's rarely a universal solution. Good luck! – Fattie Nov 15 '20 at 15:07
6

Use below code for Swift 5.0

Step 1 : Take one UIView from Storyboard, drag it into your ViewController file and Create IBOutlet of UIView.

@IBOutlet weak var viewDrag: UIView!
var panGesture = UIPanGestureRecognizer()

Step 2 : In viewDidLoad() adding the below code.

override func viewDidLoad() {
super.viewDidLoad()
  let panGesture = UIPanGestureRecognizer(target: self, action:(Selector(("draggedView:"))))
  viewDrag.isUserInteractionEnabled = true
  viewDrag.addGestureRecognizer(panGesture)
}

Step 3 : Create func and add code to move the UIView as like below.

func draggedView(sender:UIPanGestureRecognizer){
    self.view.bringSubviewToFront(viewDrag)
    let translation = sender.translation(in: self.view)
    viewDrag.center = CGPoint(x: viewDrag.center.x + translation.x, y: viewDrag.center.y + translation.y)
    sender.setTranslation(CGPoint.zero, in: self.view)
}

Hope this will help someone.

Dilip Tiwari
  • 1,441
  • 18
  • 31
0

This UIView extension makes a UIView object draggable and limits the movement to stay within the bounds of the screen.

    extension UIView {
    func makeDraggable() {
        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
        self.addGestureRecognizer(panGesture)
    }
    
    @objc func handlePan(_ gesture: UIPanGestureRecognizer) {
        guard gesture.view != nil else { return }
        
        let translation = gesture.translation(in: gesture.view?.superview)
        
        var newX = gesture.view!.center.x + translation.x
        var newY = gesture.view!.center.y + translation.y
        
        let halfWidth = gesture.view!.bounds.width / 2.0
        let halfHeight = gesture.view!.bounds.height / 2.0
        
        // Limit the movement to stay within the bounds of the screen
        newX = max(halfWidth, newX)
        newX = min(UIScreen.main.bounds.width - halfWidth, newX)
        newY = max(halfHeight, newY)
        newY = min(UIScreen.main.bounds.height - halfHeight, newY)
        
        gesture.view?.center = CGPoint(x: newX, y: newY)
        gesture.setTranslation(CGPoint.zero, in: gesture.view?.superview)
    }
}
oblomov
  • 18
  • 3