VYT VYT - 3 months ago 10
Swift Question

How to move CALayer on NSImageView

There is a subclass of NSImageView and the instance of CALayer is created, so we see a rectangle on the image. Questions is how to move this rectangle when mouse is down (when mouse pointer inside of the rectangle) and dragged. When mouse is up this rectangle (CALayer) should stay in the new position on the image.

For instance

class ImageViewWithRectangle: NSImageView
{
var shape : CAShapeLayer!

func drawRectangle()
{
shape = CAShapeLayer()
shape.lineWidth = 1.0
shape.fillColor = NSColor.clear().cgColor
shape.strokeColor = NSColor.gray().cgColor
shape.lineDashPattern = [1,1]
self.layer?.addSublayer(shape)

let path = CGMutablePath()
path.moveTo(nil, x: 1, y: 1)
path.addLineTo(nil, x: 1, y: 50)
path.addLineTo(nil, x: 50, y: 50)
path.addLineTo(nil, x: 50, y: 1)
path.closeSubpath()
self.shape.path = path

}
}

Answer

You are very close to your goal, just implement the mouse events !

Here is a working snippet:


class ImageViewWithRectangle: NSImageView {

    var shape : CAShapeLayer!

    var shapeRect = NSMakeRect(10, 10, 100, 50)

    var shouldMove = false;

    var anchorPoint : NSPoint!

    override func awakeFromNib() {

        //We MUST implement layers! Otherwise nothing will work!!
        //You could do it even through Interface Builder

        self.wantsLayer = true;

    }

    override func drawRect(dirtyRect: NSRect) {

        //Every time the view is drawn, remove the old layer
        self.layer?.sublayers?.forEach({ $0.removeFromSuperlayer() })

        //Draw the new one
        self.drawRectangle()
    }

    func drawRectangle()
    {

        //Draw the layer
        shape = CAShapeLayer()
        shape.lineWidth = 1.0
        shape.fillColor = NSColor(calibratedWhite: 1, alpha: 0).CGColor
        shape.strokeColor = NSColor.grayColor().CGColor
        shape.lineDashPattern = [1,1]
        shape.backgroundColor = NSColor.greenColor().CGColor

        //No need for CGPaths for a simple rect, just set the frame and fill it
        shape.frame = self.shapeRect

        self.layer?.addSublayer(shape)

    }

    //Implmenet mouse events
    override func mouseDown(theEvent: NSEvent) {

        //get coordinates
        let pos = theEvent.locationInWindow

        //Check if inside the rect
        if ((pos.x >= self.shapeRect.origin.x) && (pos.x <= self.shapeRect.origin.x + self.shapeRect.size.width)) {

            //X match, now check Y
            if ((pos.y >= self.shapeRect.origin.y) && (pos.y <= self.shapeRect.origin.y + self.shapeRect.size.height)) {

                //If we get here, then we're insisde the rect!
                self.shouldMove = true;

                //OPTIONAL : Set an anchor point
                self.anchorPoint = NSMakePoint(pos.x - self.shapeRect.origin.x, pos.y - self.shapeRect.origin.y);


            }

        }


    }

    override func mouseDragged(theEvent: NSEvent) {

        if (self.shouldMove) {

            let pos = theEvent.locationInWindow

            //Update rect origin, or whatever you want to use as anchor point
            self.shapeRect.origin = NSMakePoint(pos.x - self.anchorPoint.x, pos.y - self.anchorPoint.y)

            //Redraw the view
            self.display()

        }

    }

    override func mouseUp(theEvent: NSEvent) {

        if (self.shouldMove) {

            //Reset value
            self.shouldMove = false;

        }

    }

}

The output will be something like this (No bg images have been set though)


Before dragging After dragging

You could even add transition effects, borders, gradients and lots more!

CALayers and more generally CoreAnimation is really powerful!

Please let me know if you need clarifications,

I hope this helped, if so mark this answer as correct so that others can use it!

Cheers.