YakuzaNY
YakuzaNY

Reputation: 69

Dragging UIImageView only inside it's parent view

Let's say that I have:

Now in other words, I want the fox to move only inside it's fence

In my viewDidLoad():

override func viewDidLoad() {
    super.viewDidLoad()

    view.addSubview(fence)
    fence.addSubview(fox)

}

Now this part works fine, I figured to move the fox by subclassing UIImageView with a little bit of modifications:

class DraggableImageView: UIImageView {


var dragStartPositionRelativeToCenter : CGPoint?

override init(image: UIImage!) {
    super.init(image: image)

    self.isUserInteractionEnabled = true 

    addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePan(nizer:))))

    layer.shadowColor = UIColor.black.cgColor
    layer.shadowOffset = CGSize(width: 0, height: 3)
    layer.shadowOpacity = 0.5
    layer.shadowRadius = 2
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

@objc func handlePan(nizer: UIPanGestureRecognizer!) {
    if nizer.state == UIGestureRecognizer.State.began {
        let locationInView = nizer.location(in: superview)
        dragStartPositionRelativeToCenter = CGPoint(x: locationInView.x - center.x, y: locationInView.y - center.y)

        layer.shadowOffset = CGSize(width: 0, height: 20)
        layer.shadowOpacity = 0.3
        layer.shadowRadius = 6

        return
    }

    if nizer.state == UIGestureRecognizer.State.ended {
        dragStartPositionRelativeToCenter = nil

        layer.shadowOffset = CGSize(width: 0, height: 3)
        layer.shadowOpacity = 0.5
        layer.shadowRadius = 2

        return
    }

    let locationInView = nizer.location(in: superview)

    UIView.animate(withDuration: 0.1) {
        self.center = CGPoint(x: locationInView.x - self.dragStartPositionRelativeToCenter!.x,
                              y: locationInView.y - self.dragStartPositionRelativeToCenter!.y)
    }
}}

Now I can drag the fox object anywhere i like, but; what if I wanted to only move the fox inside the fence object?, since it's a subview, I think it's possible.

Upvotes: 1

Views: 62

Answers (1)

Codi Burley
Codi Burley

Reputation: 127

In order to keep the image view inside its parent, I added a check before you update the center of the view that makes sure that the views frame will be in the parents frame. The center is only updated if the update would keep the view within its parent's frame.

I also updated the pan handler to use the translation (similar to the example in the pan gesture documentation) as opposed to the locationInView. This makes the drag behave better.

I've tested this and I believe it behaves in the way you desire. Hope this helps:

    @objc func handlePan(nizer: UIPanGestureRecognizer!) {
        if nizer.state == UIGestureRecognizer.State.began {
            dragStartPositionRelativeToCenter = self.center
            layer.shadowOffset = CGSize(width: 0, height: 20)
            layer.shadowOpacity = 0.3
            layer.shadowRadius = 6

            return
        }

        if nizer.state == UIGestureRecognizer.State.ended {
            dragStartPositionRelativeToCenter = nil

            layer.shadowOffset = CGSize(width: 0, height: 3)
            layer.shadowOpacity = 0.5
            layer.shadowRadius = 2

            return
        }

        if let initialCenter = dragStartPositionRelativeToCenter {
            let translation = nizer.translation(in: self.superview)
            let newCenter = CGPoint(x: initialCenter.x + translation.x, y: initialCenter.y + translation.y)
            if frameContainsFrameFromCenter(containingFrame: superview!.frame, containedFrame: self.frame, center: newCenter) {
                UIView.animate(withDuration: 0.1) {
                    self.center = newCenter
                }
            }

        }
    }

    func frameContainsFrameFromCenter(containingFrame: CGRect, containedFrame: CGRect, center: CGPoint) -> Bool {
        let leftMargin = containedFrame.width / 2
        let topMargin = containedFrame.height / 2
        let testFrame = CGRect(
            x: leftMargin,
            y: topMargin,
            width: containingFrame.width - (2*leftMargin),
            height: containingFrame.height - (2*topMargin)
        )
        return testFrame.contains(center)
    }

Upvotes: 3

Related Questions