Custom NSStatusItem and NSView not reliably receiving NSTrackingEvents
Asked Answered
J

2

2

I have a Status-Bar item only app that Iam trying to get to show a panel on mouseOver. I have the custom status item (and associated view) hooked up and working, but the tracking rect is only receiving events on every dozen or so launches. This leads me to believe there is a race condition happening somewhere, but I can't find it. In my custom status bar item view:

- (id)initWithStatusItem:(NSStatusItem *)statusItem {
    CGFloat itemWidth = [statusItem length];
    CGFloat itemHeight = [[NSStatusBar systemStatusBar] thickness];
    NSRect itemRect = NSMakeRect(0.0, 0.0, itemWidth, itemHeight);
    NSLog(@"itemRect: %@", NSStringFromRect(itemRect));

    if ((self = [super initWithFrame:itemRect])) {
        _statusItem = statusItem;
        _statusItem.view = self;

        NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways;
        NSTrackingArea *trackingArea = [[NSTrackingArea alloc] initWithRect:itemRect
                                                                    options:options
                                                                      owner:self
                                                                   userInfo:nil];
        [self addTrackingArea:trackingArea];

        [self.window setIgnoresMouseEvents:NO];
        [self.window setAcceptsMouseMovedEvents:YES];

        self.wantsLayer = YES;
    }
    return self;
}

- (void)mouseEntered:(NSEvent *)theEvent {
    [[NSNotificationCenter defaultCenter] postNotificationName:UAStatusItemMouseEnteredNotification object:nil];
}

- (void)mouseExited:(NSEvent *)theEvent {
    [[NSNotificationCenter defaultCenter] postNotificationName:UAStatusItemMouseExitedNotification object:nil];
}

On most launches, the app does not respond to the tracking mouse events, but every so often, the mouseEntered: and mouseExited: methods are being called properly, completely confusing me. What is going on here and what am I doing wrong?




EDIT 07/17/2012
I altered the code based on @Streams's answer, but an seeing the same problem:

- (id)initWithStatusItem:(NSStatusItem *)statusItem {
    CGFloat itemWidth = [statusItem length];
    CGFloat itemHeight = [[NSStatusBar systemStatusBar] thickness];
    NSRect itemRect = NSMakeRect(0.0, 0.0, itemWidth, itemHeight);
    NSLog(@"itemRect: %@", NSStringFromRect(itemRect));

    if ((self = [super initWithFrame:itemRect])) {
        _statusItem = statusItem;
        _statusItem.view = self;            

        [self updateTrackingAreas];

        [self.window setIgnoresMouseEvents:NO];
        [self.window setAcceptsMouseMovedEvents:YES];

        self.wantsLayer = YES;
    }
    return self;
}

- (void)updateTrackingAreas {

    if (self.trackingArea)
        [self removeTrackingArea:self.trackingArea];

    [super updateTrackingAreas];

    self.trackingArea = [[NSTrackingArea alloc] initWithRect:CGRectZero
                                                     options:NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingInVisibleRect | NSTrackingActiveAlways
                                                       owner:self
                                                    userInfo:nil];
    [self addTrackingArea:self.trackingArea];
}




EDIT 07/18/2012
Here is a barebones sample project that uses a well know github project (written by @Stream) to show the problem. It cannot receive the mouseover events reliably, if at all.

Joy answered 17/7, 2012 at 15:5 Comment(2)
Strangely, though, the sample project works perfectly on my system.Grammer
@Grammer Does it work perfectly every single run of the app?Joy
J
3

I opened a DTS request to have Apple take a look at this. Here is the response:

...you are using full screen in Xcode when starting your app. I wasn't doing this [before], but I now can reproduce the issue. From what I can tell it only happens when your app is started from full screen mode in Xcode. Your users won't be starting the app this way. This is a problem with AppKit's fullScreen mode, and not necessarily with your code.

Joy answered 23/7, 2012 at 22:35 Comment(1)
I have the same issue that I can reproduce when launching my app when I'm in fullscreen mode. BUT, it still occasionally doesn't work even though I'm not in fullscreen mode during launch. Sounds like it's the same for you? Have you managed to solve this?Obstinacy
W
0

I believe you should manage tracking areas only in -[NSView updateTrackingAreas]. For example:

- (void)updateTrackingAreas
{
    if (_trackingArea) {
        [self removeTrackingArea:_trackingArea];
    }

    [super updateTrackingAreas];

    NSTrackingAreaOptions options = (NSTrackingMouseEnteredAndExited |
                                     NSTrackingMouseMoved |
                                     NSTrackingInVisibleRect |
                                     NSTrackingActiveAlways);
    _trackingArea = [[NSTrackingArea alloc] initWithRect:CGRectZero
                                                 options:options
                                                   owner:self
                                                userInfo:nil];
    [self addTrackingArea:_trackingArea];
}
Wavemeter answered 17/7, 2012 at 22:18 Comment(8)
For a view that doesn't change size, it is ok in init, but the key to the answer lies here anyway! Changing my "rect" variable to CGRectZero as you have here now causes it to work every time. According to the docs, the rect is in the coordinate space of the receiver, that is why I was sending it with self.bounds. I don't know why that wasn't working, but the CGRectZero is so this is the correct answer. You have been more than helpful :)Joy
Looks like I spoke too soon. Seems to be intermittent as well.Joy
I updated with new code as per your recommendation. Still haven't been able to duplicate the result after the first 3 times I tried before writing the first comment.Joy
Alternatively, you would setup an event monitor to track when the mouse position is within your status view. Even regular NSTimer with +[NSEvent mouseLocation]could work.Wavemeter
This is just so strange because one of every dozen or so launches, the app works great. This code works exactly as intended. Do you think the status bar might have some internal caching that is somehow interfering with the NSTrackingRect being applied?Joy
I added a sample project with this code added to your barebones Popup repo. github.com/shpakovski/PopupJoy
File a Radar or post this question on Apple Developer Forums if nothing helps :)Wavemeter
updateTrackingRect is not necessary when using NSTrackingInVisibleRect as the size never changes. This answer is not applicable here.Joy

© 2022 - 2024 — McMap. All rights reserved.