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.
The 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.
Jonathan Watmough 18:14 on September 4, 2008 Permalink |
Hi Jonathan,
Interesting post and project. It was funny to run it, because it looks like it’s out by a pixel, but then, looking at iTunes, that now looks wrong to me. Thanks for ruining iTunes for me!
Anyway, Scribbler sounds like a great idea. I use a really cut down selection of styles to give my documents some regularity in OpenOffice. When using other peoples documents in Word, the generally messed up styles drives me nuts. Will Scribbler have a set of predefined document templates? It seems like the nice TeX output would be great for people who like documents to be formatted in a standard way.
How are you planning to ensure that a TeX stack exists and is valid on the destination Mac?
Good luck with being an ISV!
Jonathan Dann 20:34 on September 5, 2008 Permalink |
Hi Jonathan,
I *think* I know what you mean about it being out by a pixel, is it on the right-hand side of the vertical scroller? If it is then you see it in apps that don’t have a 1-pixel border around their scroll views, so the edge of the scroller is exactly aligned with the edge of the window. You can then see the effect this has when you move the window so it is directly above the desktop after being above another application’s window (if that makes sense). If that’s not what you mean then et me know and I’ll take a look.
As for Scribbler, I’m working on a set of project templates and document templates to help get people started, but these will grow as they’re added by users. If you have your own, you won’t have to send it to me for inclusion in the app either, just put it in the Scribbler directory in ~/Library/Application Support and Scribbler will find it. If you have any really good templates then please send them along!
As for the TeX stack, the only part that doesn’t work if there’s no valid distro on the system is typesetting itself. For those that download the app without TeX installed, I’m going to point them to the most current MacTeX distro for simplicity, possibly downloading it and mounting the disk image it for them so all they have to do is install. If I’ve misunderstood your question then please forgive me!
Jonathan 03:15 on February 24, 2009 Permalink |
This is awesome. I especially appreciate the fact that you aren’t using images. I’ve been taking a lot of extra time to draw out my own interfaces lately. I just have one problem now. I realized it after I threw your scroll bars in. My contained table view still has Aqua headers. Know of any other Classes for taking care of that or do i need to get to work? Also I’m planning on modifying this to also accommodate the black/grey scroll bars that you also see in iTunes in addition to others like iMovie. Want me to send you my changes when/if I finish it?
Jonathan 09:33 on February 24, 2009 Permalink |
Hi Jonathan,
Glad you like them, I’m afraid looking back on them now they need a bit of a redesign! I’ve just been thinking of using black ones myself so, yeah, send them along when you’re happy with them. I’d appreciate it.
As for the headers, you just have to subclass NSTableHeaderCell. You’ll have to draw both the background and the text but read the 10.5 AppKit release notes, they give you good pointers on using NSBackgroundStyle in cells for the embossed text look.
I’ll see what I dig up in my own source though as I’m sure I did this a while back.
Jonathan Badeen 17:59 on February 26, 2009 Permalink |
Thanks Jonathan,
I’ve been working on finessing your scroll bars in addition to adding the dark styled ones. There are still at least a couple things i need to do/improve still. You can see them implemented in my app here – http://www.badeen.com/fastcapture1.png. When I’m done I’ll send it your way. I did have one question though. Do you know how I can go about drawing my own resize corner or just get it to display transparently in the lower right. I essentially would like to get it to look more like it does when there is no scroll bar – http://www.badeen.com/fastcapture2.png
Jonathan Badeen 04:56 on February 28, 2009 Permalink |
whoops, sorry! http://www.badeen.com/screenshots/fastcapture1.png and http://www.badeen.com/screenshots/fastcapture2.png
Gustavo 09:09 on September 6, 2009 Permalink |
Jonathan hello.
Thanks great example, I wish I understand it better, im a novice ( with some experience already) and I thought this goal wold be easier than I say in the example.. Im trying to implement something similar in my application, but definitely you have gave me a good idea on how to achieve it.
Must read again to understand the whole of what you are doing in the example… :S
G.
Michael 06:58 on October 6, 2009 Permalink |
Hey Jonathan.
Thanks for the amazing NSScrollerView subclass. I’m currently working on a project made to look similar to the itunes store. http://www.publictunes.com/screenshots/pic1.png the vertical scroller works like a charm. Unfortunately the horizontal one appears to have some fundamental drawing errors. I have looked at the code and had trouble grasping how the curves were made that cut into the increment decrement arrows for the slider tracks. If you could please explain this or fix the class it would be much appreciated.
-Mike
Michael 07:00 on October 6, 2009 Permalink |
Oh i forgot. Here is a blow up of the horizontal slider problems. http://www.publictunes.com/screenshots/pic2.png
bryscomat 16:48 on October 26, 2009 Permalink
I was able to fix this by putting this in the arrowsSetting: method:
ESScrollerArrowsSetting setting;
if (self.isVertical) {
if (NSMaxY([self rectForPart:NSScrollerDecrementLine]) == NSMinY([self rectForPart:NSScrollerIncrementLine]))
setting = ESScrollerArrowsTogether;
else
setting = ESScrollerArrowsApart;
}
else {
if (NSMaxX([self rectForPart:NSScrollerDecrementLine]) == NSMinX([self rectForPart:NSScrollerIncrementLine]))
setting = ESScrollerArrowsTogether;
else
setting = ESScrollerArrowsApart;
}
return setting;
Philipp 13:47 on October 25, 2009 Permalink |
Hi Jonathan,
Thank you for the great post and the code.
What license is the code released under? I looked through the project folder but couldn’t find any.
Thank you in advance!
Jonathan Dann 12:39 on October 31, 2009 Permalink |
Oh yeah, consider it BSD If you want to credit me feel free, but you don’t have to.
I need to clean up that code. It’s embarrassingly bad now!
Jonathan Dann 12:40 on October 31, 2009 Permalink
Sorry about that. I must have got horribly distracted. I’ll update the code soon! It need s a good looking-at.