Search code examples
iosswift3draguipangesturerecognizer

Drag UIButton without it shifting to center [Swift 3]


So I found out how to make a button draggable using the UIPanGestureRecognizer. But the only way I know how to do it is by storing and dragging the button by the center. The problem with this is if you try and drag the button from a corner, the button instantly shifts from the corner to the center. What I'm looking for is a solution that would keep my finger on a selected place while moving without instantly locking onto the center.

The code I'm currently using:

func buttonDrag(pan: UIPanGestureRecognizer) {
    print("Being Dragged")
    if pan.state == .began {
        print("panIF")
        buttonCenter = button.center // store old button center
    }else {
        print("panELSE")
        let location = pan.location(in: view) // get pan location
        button.center = location // set button to where finger is
    }
}

Thanks in advance.


Solution

  • This can be done at least in two different ways, one using GestureRecognizer your question way and other way is subclassing the UIView and implementing the touchesBegan, touchesMoved , touchesEnded, touchesCancelled in general will work for any UIView subclass can be UIButton or UILabel or UIImageView etc...

    In your way, using GestureRecognizer I make a few changes you still require a var to keep the origin CGPoint of the touch in your UIButton so we get the touch position relative to the UIButton and when the drag continue adjust the UIButton origin according to the origin touch position and the positions of the movement

    Method 1 GestureRecognizer

    import UIKit
    
    class ViewController: UIViewController {
    
        @IBOutlet weak var button: UIButton!
        var buttonOrigin : CGPoint = CGPoint(x: 0, y: 0)
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
            let gesture = UIPanGestureRecognizer(target: self, action: #selector(buttonDrag(pan:)))
            self.button.addGestureRecognizer(gesture)
        }
    
        func buttonDrag(pan: UIPanGestureRecognizer) {
            print("Being Dragged")
            if pan.state == .began {
                print("panIF")
                buttonOrigin = pan.location(in: button)
            }else {
                print("panELSE")
                let location = pan.location(in: view) // get pan location
                button.frame.origin = CGPoint(x: location.x - buttonOrigin.x, y: location.y - buttonOrigin.y)
            }
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
    
    }
    

    Method 2 UIView subclass in this case UIButton subclass

    Use this UIButton subclass

    import UIKit
    
    class DraggableButton: UIButton {
    
        var localTouchPosition : CGPoint?
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
    
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            super.touchesBegan(touches, with: event)
            let touch = touches.first
            self.localTouchPosition = touch?.preciseLocation(in: self)
        }
    
        override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)
        let touch = touches.first
        guard let location = touch?.location(in: self.superview), let localTouchPosition = self.localTouchPosition else{
            return
        }
    
        self.frame.origin = CGPoint(x: location.x - localTouchPosition.x, y: location.y - localTouchPosition.y)
        }
    
        override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
            super.touchesEnded(touches, with: event)
            self.localTouchPosition = nil
        }
    
        override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
            super.touchesCancelled(touches, with: event)
            self.localTouchPosition = nil
        }
    
    }
    

    Result

    enter image description here

    enter image description here

    Hope this helps you