2

I have a layer-backed NSView. When the mouse enters the view, I want to draw a big circle wherever the mouse is. Basically track the mouse position. Unfortunately, the circle is lagging behind the actual mouse position.

I think it has something to do with implicit animations. Disabling animations for the "content" property of the layer doesn't have any effect.

begin Edit: How can I draw a circle at the position of the mouse cursor while the mouse is moving without the circle lagging behind the mouse cursor? :end Edit

Here is the code I have.

class TrackingView : NSView
{
override init(frame frameRect: NSRect)
{
    super.init(frame: frameRect)
    self.wantsLayer = true
}

required init?(coder: NSCoder)
{
    super.init(coder: coder)
    self.wantsLayer = true
}

//layer backing
override var wantsUpdateLayer : Bool { return true }

override func makeBackingLayer() -> CALayer
{
    return TrackingLayer()
}

//tracking areas
override func updateTrackingAreas()
{
    //remove current tracking area's
    while self.trackingAreas.count > 0
    {
        self.removeTrackingArea(self.trackingAreas.first as! NSTrackingArea)
    }

    if let trackingFrames = (self.layer as? TrackingLayer)?.trackingFrames
    {
        for trackingFrame in trackingFrames
        {
            let trackingArea = NSTrackingArea(
                rect: trackingFrame,
                options: NSTrackingAreaOptions.MouseEnteredAndExited | NSTrackingAreaOptions.ActiveInActiveApp | NSTrackingAreaOptions.MouseMoved,
                owner: self,
                userInfo: nil)
            self.addTrackingArea(trackingArea)
        }
    }
}

//mouse
override func mouseEntered(event: NSEvent)
{
    (self.layer as? TrackingLayer)?.userLocation = self.convertPoint(event.locationInWindow, fromView: nil)
}

override func mouseMoved(event: NSEvent)
{
    (self.layer as? TrackingLayer)?.userLocation = self.convertPoint(event.locationInWindow, fromView: nil)
}
}

class TrackingLayer : CALayer
{
var trackingFrames : [NSRect] { return [self.frame] }
var userLocation : CGPoint? {
    didSet { self.setNeedsDisplay() }
}

override init!()
{
    super.init()
    self.disableAnimations()
    self.backgroundColor = NSColor.whiteColor().CGColor
}
override init!(layer: AnyObject!)
{
    super.init(layer: layer)
    self.disableAnimations()
}

required init(coder aDecoder: NSCoder)
{
    super.init(coder: aDecoder)
    self.disableAnimations()
}

//animations
private func disableAnimations()
{
    self.actions = [
        "content"   : NSNull(),
        "sublayers" : NSNull()]
}

//drawing
override func drawInContext(context: CGContext!)
{
    if var assumedUserLocation = userLocation
    {
        CGContextStrokeEllipseInRect(context, CGRectMake(assumedUserLocation.x-5, assumedUserLocation.y-5, 10, 10))
        CGContextFillEllipseInRect(context, CGRectMake(assumedUserLocation.x-5, assumedUserLocation.y-5, 10, 10))
    }
}
}
Elise van Looij
  • 4,162
  • 3
  • 29
  • 52
user965972
  • 2,489
  • 2
  • 23
  • 39

1 Answers1

0

Disable CALayers animations for the change you are making

CATransaction.withDisabledActions {
   hitLayer.move(by: dx, y: dy)
}


extension CATransaction {
    class func withDisabledActions<T>(_ body: () throws -> T) rethrows -> T {
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        defer {
            CATransaction.commit()
        }
        return try body()
    }
}

Also check out this as it might be easier approach

Drawing selection box (rubberbanding, marching ants) in Cocoa, ObjectiveC

Duncan Groenewald
  • 8,496
  • 6
  • 41
  • 76