Tracking rectangles in NSViews, eh? They’re kind of crap. Getting the events is a bit hit-and-miss, and if you move the mouse too fast you’ll often find that you miss a mouseExited: event and your view is left looking stupid. It’s an old problem, and it’s very annoying. It certainly annoys me.
Rumour has it that Leopard has a new class called NSTrackingArea which allows for more robust mouse-tracking, but Leopard isn’t out yet as I write this, and you still have the problem of supporting previous versions of Mac OS X even when we do have Leopard.
So, here’s a possible solution. I’ve created MATrackingArea: a simple category on NSView and an accompanying class which together allow you to add “tracking areas” to your views. Tracking areas are like tracking rectangles but are more robust and effective, and can be configured to track the mouse either at all times, only when the view’s app is active, only when the view’s window is key, or only when the view is First Responder. You can also choose whether to use the view’s visible rect in preference to the rect you pass in, whether the events should still be sent during drags, and so on. See the example for more details.
The demo project requires Xcode 2.4 and Mac OS X 10.4 (Tiger) or later, but if you just take the source files and create your own project, it should work right back to Mac OS X 10.2 (Jaguar). You could probably even make it work on 10.1 (Puma) and earlier if you remove the keyed archiving code.
Fair warning: it uses a polling timer to do its thing, but in my tests it consumes very, very little CPU (0.1% in Activity Monitor), and it will only poll if there are actually some tracking areas set up.
You can grab it, as usual, from the Cocoa source code page.
Update 1: The code has been updated today (1pm GMT, Thurs 27th Sept 2007) to alter how the MATrackingArea class stores its notion of whether the pointer was inside a given area on the last check, to improve efficiency.
Update 2: The code has been updated again today (4:30 pm GMT, Thurs 27th Sept 2007) to allow updating the tracked rectangle of an existing tracking area. This means that you no longer need to remove and recreate the tracking area whenever your view’s bounds change. You can still do it in the old remove-and-recreate way if you like, but I think the new method is a lot more convenient. Upon changing the rectangle, MATrackingArea will of course update immediately.
Update 3: Another update (12:35 pm GMT, Weds 3rd Oct 2007) to remove the unnecessary NSView category, which also caused conflicts with new NSView methods on Leopard which had the same signature. The sample app has been updated too, and this code should run on Mac OS X 10.2 - 10.5 inclusive.
Update 4: Updated yet again (11:10 pm GMT, Weds 3rd Oct 2007) to fix a bug where a tracking area could be incorrectly triggered whilst dragging the window which the tracking area belonged to. This was because NSWindow’s -frame method doesn’t report the actual current frame of the window, but rather the cached frame, which isn’t updated constantly during a window drag. Many thanks indeed to Wolf Rentzsch for the bug report and for the code to fix it.