39

I created a subclass of NSImageView to capture mouseEntered and mouseExited events. But only mouseUp and mouseDown events are getting called. How to capture the mouseEntered and mouseExited events in NSImageView subclass?

Justin Boo
  • 10,132
  • 8
  • 50
  • 71
Ram
  • 1,872
  • 5
  • 31
  • 54

4 Answers4

85

If You want to use mouseEntered: and mouseExited: You need to use NSTrackingArea. Here is reference NSTrackingArea Class Reference.

Example:

//Add this to Your imageView subclass

-(void)mouseEntered:(NSEvent *)theEvent {
    NSLog(@"Mouse entered");
}

-(void)mouseExited:(NSEvent *)theEvent
{
    NSLog(@"Mouse exited");
}

-(void)updateTrackingAreas
{ 
    [super updateTrackingAreas];
    if(trackingArea != nil) {
        [self removeTrackingArea:trackingArea];
        [trackingArea release];
    }

    int opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways);
    trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
                                                 options:opts
                                                   owner:self
                                                userInfo:nil];
    [self addTrackingArea:trackingArea];
}
Deadpikle
  • 356
  • 6
  • 22
Justin Boo
  • 10,132
  • 8
  • 50
  • 71
  • 17
    @ArtOfWarfare Or it is the reality of having a complex view hierarchy and not wanting to invoke potentially hundreds of thousands of methods when the user merely slides the pointer over a hunk of UI. This is the age old **Never poll, always pull.** – bbum Mar 20 '15 at 16:21
  • Bingoo, Worked like charm – Pete Apr 13 '15 at 09:34
  • 5
    @Edward What? Pull vs. Poll? No; **Significantly** more relevant in 2016 than 1988. In 1988, devices were all powered from the wall. In 2016, the majority of devices are powered by a battery and *polling* is devastating to battery life. – bbum Apr 12 '16 at 19:35
  • 1
    @bbum just realized you work at the mothership. I bow. – Edward Apr 12 '16 at 19:45
  • @Edward I've been at this ObjC thing for a long, long time. :) – bbum Apr 13 '16 at 19:43
  • 2
    "Invoke hundereds and thousands of methods when the user merely slides the pointer"? What is there to invoke if no callback function is defined? C# has the exact same callback for all controls, and you don't have to setup weird insider snippets of code, buried inside tones of documentation. If you've defined a callback for a control it gets invoked, otherwise nothing. As simple as that. I don't get what's so different in OS X stack that makes this necessary. – Hashman May 06 '16 at 03:19
  • 1
    Few things missing from this solution, see: http://stackoverflow.com/questions/8979639/mouseexited-isnt-called-when-mouse-leaves-trackingarea-while-scrolling – Elise van Looij Apr 23 '17 at 14:45
  • Why are we running this code everytime we updateTrackingAreas()? Would it be better to put in the init() of the NSImageView? – Ricardo Anjos Dec 14 '18 at 10:40
  • 1
    @RicardoAnjos updateTrackingAreas is the appropriate place for this in general. For example, if this were somewhere earlier (e.g. in init) it wouldn't be properly recreated when the view was moved or resized. – clarkcox3 Dec 19 '18 at 22:12
13

Swift 4 version of Justin Boo's answer

override func updateTrackingAreas() {
    super.updateTrackingAreas()

    for trackingArea in self.trackingAreas {
        self.removeTrackingArea(trackingArea)
    }
    
    let options: NSTrackingArea.Options = [.mouseEnteredAndExited, .activeAlways]
    let trackingArea = NSTrackingArea(rect: self.bounds, options: options, owner: self, userInfo: nil)
    self.addTrackingArea(trackingArea)
}
Hejazi
  • 16,587
  • 9
  • 52
  • 67
Kiran
  • 617
  • 6
  • 20
  • 1
    Can/should we not edit existing answers to update them to the latest version of a language? – STO Sep 07 '18 at 09:11
11

Swift 3 Version of @Justin Boo's answer:

private var trackingArea: NSTrackingArea?

override func updateTrackingAreas() {
    super.updateTrackingAreas()

    if let trackingArea = self.trackingArea {
        self.removeTrackingArea(trackingArea)
    }

    let options: NSTrackingAreaOptions = [.mouseEnteredAndExited, .activeAlways]
    let trackingArea = NSTrackingArea(rect: self.bounds, options: options, owner: self, userInfo: nil)
    self.addTrackingArea(trackingArea)
}
Nikolay Suvandzhiev
  • 8,465
  • 6
  • 41
  • 47
4

C# Xamarin version of Justin Boo's answer

public override void UpdateTrackingAreas ()
{
    base.UpdateTrackingAreas ();

    foreach (var item in TrackingAreas ()) {
        RemoveTrackingArea (item);
    }

    var options = NSTrackingAreaOptions.MouseEnteredAndExited | NSTrackingAreaOptions.ActiveAlways;

    var trackingArea = new NSTrackingArea (this.Bounds, options, this, null);

    AddTrackingArea (trackingArea);
}
Dominique
  • 817
  • 1
  • 5
  • 10