Search code examples
swiftuigesturerecognizerdraguipangesturerecognizercgrect

Drag a CGRect using UIPanGestureRecognizer


I am subclassing UIView, and am trying to "drag" a CGRect back and forth across the screen. Basically I want to move(redraw) the rectangle every time I drag my finger. So far, I have this code:

var rectangle: CGRect {
    get {
        return CGRect(x: 200,
                    y: 200,
                    width: frame.width / 6,
                    height: 15)
    }

    set {}
}

override func draw(_ rect: CGRect) {
    let gesture = UIPanGestureRecognizer(target: self, action: #selector(dragRectangle(recognizer:)))
    addGestureRecognizer(gesture)
    drawRectangle(rect)
}

func drawRectangle(_ rect: CGRect) {
    let path = UIBezierPath(rect: rectangle)
    UIColor.black.set()
    path.fill()
}

@objc func dragRectangle(recognizer: UIPanGestureRecognizer) {
    let translation = recognizer.translation(in: self)
    rectangle = CGRect(x: rectangle.midX + translation.x, y: rectangle.midY + translation.y, width: rectangle.width, height: rectangle.height)
    setNeedsDisplay()
    recognizer.setTranslation(CGPoint.zero, in: self)
}

This is my first time using UIPanGestureRecognizer, so I'm not sure of all the details that go into this. I have set breakpoints in drawRectangle and confirmed that this is being called. However, the rectangle on the screen does not move at all, no matter how many times I try to drag it. What's wrong?


Solution

  • Try like this (check comments through code):

    @IBDesignable
    class Rectangle: UIView {
    
        @IBInspectable var color: UIColor = .clear {
            didSet { backgroundColor = color }
        }
        // draw your view using the background color
        override func draw(_ rect: CGRect) {
            backgroundColor?.set()
            UIBezierPath(rect: rect).fill()
        }
        // add the gesture recognizer to your view
        override func didMoveToSuperview() {
            addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(pan)))
        }
        // your gesture selector
        @objc func pan(_ gesture: UIPanGestureRecognizer) {
            //  update your view frame origin
            frame.origin += gesture.translation(in: self)  
            // reset the gesture translation
            gesture.setTranslation(.zero, in: self)
        }
    }
    

    extension CGPoint {
        static func +=(lhs: inout CGPoint, rhs: CGPoint) {
            lhs.x += rhs.x
            lhs.y += rhs.y
        }
    }
    

    To draw rectangles on your view when panning you can do as follow:

    import UIKit
    
    class ViewController: UIViewController {
        var rectangles: [Rectangle] = []
        override func viewDidLoad() {
            super.viewDidLoad()
            view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(pan)))
        }
        @objc func pan(_ gesture: UIPanGestureRecognizer) {
            switch gesture.state {
            case .began:
                let rectangle = Rectangle(frame: .init(origin: gesture.location(in: view), size: .init(width: 0, height: 0)))
                rectangle.fillColor = .red
                rectangle.strokeColor = .white
                rectangle.lineWidth = 3
                view.addSubview(rectangle)
                rectangles.append(rectangle)
            case .changed:
                let distance = gesture.translation(in: view)
                let index = rectangles.index(before: rectangles.endIndex)
                let frame = rectangles[index].frame
                rectangles[index].frame = .init(origin: frame.origin, size: .init(width: frame.width + distance.x, height: frame.height + distance.y))
                rectangles[index].setNeedsDisplay()
                gesture.setTranslation(.zero, in: view)
            case .ended:
                break
            default:
                break
            }
        }
    }
    

    Sample Project