Creating iTunes Scrollers
Apple introduced new scrollers in iTunes 7 and then moved on to give us the HUD, which many developers want their own scrollers for too. In Leopard, many of us thought that these would come in a nice, shiny box; but as they didn’t we’re all forced to roll our own. The common method is to draw all the components in Photoshop and then make a composite image when subclassing, but now with
NSGradient and some nice additions to
NSBezierPath all these have become quite easy to do, even for those with little artistic ability.
My first attempt was to do all of this using
NSAffineTransforms, but without being able to mirror a bezier path, or define the the centre of rotation of a path or NSRect it becomes a loathsome task of tweaking and it just never quite works. So all the elements are drawn separately in this example, which is more code, but turned out to be far easier to code.
The important point to realise when subclassing
NSScroller is that Apple’s documentation is out of date and some of the methods that we immediately assume we can subclass aren’t called. You can read the details in this thread on the cocoa-dev mailing list. You end up having to also implement -drawRect: and calling these documented (but unused) methods yourself.
NSScroller consists first of the
NSScrollerKnobSlot, the track on which the
NSScrollerKnob itself lies. There are 2 (normally) buttons: in the vertical case the up arrow defined as the
NSScrollerDecrementLine and the down arrow is the
NSScrollerIncrementLine. I’ve implemented a method that checks to see which configuration the user has the scrollbars in, whether they are at the top and bottom or both together at the bottom. It does this by examining the
AppleScrollBarVariant key in the global user defaults. If this (AFAICT undocumented) key changes then it calculated which configuration the user has selected. So when using these scrollers, the user can switch the configuration in System Preferences and the change can be seen. In the mode with both together at the bottom of the bar, there is also a top ‘socket’ that’s drawn.
The scrollbars also support horizontal scrolling, and I found that instantiating the vertical scroller without a frame works fine, but the only way to get a horizontal scroller is by using
-initWithFrame:. I can only assume that apple are doing something under-the-hood for horizontal ones.
The final hiccup comes when you can to support the last 20% of users that use the scrollbar variant with both buttons at both ends. This is done in terminal and thankfully requires a restart of any program in which you want it to take effect. Problems come when trying to support this case when you want to know which button has been hit so you can draw it highlighted. The documented stuff from apple returns the same value whether the decrement line one end or the other is clicked! Making this impossible without tracking the mouse yourself, and I haven’t had the time to fix that. If I were using a program that didn’t respect my settings (I’m looking at you General Electric), then I’d get angry, so I included an
NSScrollView subclass that checks for this case and just uses Apple’s scrollers if the user has selected it.
I think they’re pretty close to the iTunes ones, but mine are resoution-independent, but only Apple can give the perfect solution. You can download the XCode (3.1) project and sample app (10.5-only) here.