Home »

If you’ve used any recent version of iPhoto or iTunes, you’re doubtless aware of the whizzy-looking view they use to present your photos and music. As it so happens, an application I’m working on could also benefit from such a view, so I set out to recreate its general appearance. What follows is a brief overview of implementing a similar interface using NSCollectionView.

Obviously, the first thing you’re going to need to do is to add an NSCollectionView to your application window in Interface Builder. Once you’ve done so, bring up the Inspector and configure it to appear thusly:

Inspector Settings

This will allow you to handle selecting items, and give the collection view the dark gray color used in iPhoto and iTunes’ album cover view. If you want, you can also check the Allows Multiple Selection box to, amazingly enough, allow multiple selections. Once you’ve done this, bind the collection view’s Content to an NSArrayController’s arrangedObjects key and the Selection Indexes to the array controller’s selectionIndexes.

With the GUI set up, it’s time to write some code! This code assumes that garbage collection is enabled, and only requires OS X 10.5 or later. You’ll need to create 2 classes, one of which is a subclass of NSCollectionViewItem and the other of which is a subclass of NSView. First up is ADCollectionViewItem:

  1. //  ADCollectionViewItem.h
  2.  
  3. #import
  4.  
  5. @interface ADCollectionViewItem : NSCollectionViewItem
  6. {
  7. }
  8.  
  9. @end
  10.  
  11. //ADCollectionViewItem.m
  12.  
  13. #import "ADCollectionViewItem.h"
  14.  
  15. @implementation ADCollectionViewItem
  16.  
  17. - (void)awakeFromNib
  18. {
  19.      [[self view] bind:@"image" toObject:self withKeyPath:@"representedObject.image" options:nil];
  20.      [[self view] bind:@"imageDescription" toObject:self withKeyPath:@"representedObject.imageDescription" options:nil];
  21.      [[self view] bind:@"selected" toObject:self withKeyPath:@"selected" options:nil];
  22. }
  23.  
  24. @end

All in all, pretty straightforward – just bind the collection view item’s view to its selected status. You don’t technically need a subclass for this, but it doesn’t really hurt, especially if you want to tweak it further. Next up is the juicy bit – ADCollectionViewItemView:

  1. //  ADCollectionViewItemView.h
  2.  
  3. #import <Cocoa/Cocoa.h>
  4. #import "ADGenderGuesser.h"
  5.  
  6. @interface ADCollectionViewItemView : NSView
  7. {
  8.         NSString *imageDescription;
  9.         NSData *image;
  10.         BOOL selected;
  11.         BOOL drawBezel;
  12.         BOOL gender;
  13.        
  14.         int trackingRect;
  15. }
  16.  
  17. @property(copy) NSString *imageDescription;
  18. @property(copy) NSData *image;
  19.  
  20. @end
  21.  
  22. //  ADCollectionViewItemView.m
  23.  
  24. #import "ADCollectionViewItemView.h"
  25.  
  26. @implementation ADCollectionViewItemView
  27.  
  28. @synthesize imageDescription;
  29. @synthesize image;
  30.  
  31. - (void)setFrame:(NSRect)rect
  32. {
  33.         if (trackingRect)
  34.         {
  35.                 [self removeTrackingRect:trackingRect];
  36.                 trackingRect = [self addTrackingRect:NSMakeRect(0, 0, [self frame].size.width, [self frame].size.height) owner:self userData:nil assumeInside:NO];
  37.                 gender = [[[ADGenderGuesser alloc] init] gender];
  38.         }
  39.         else
  40.         {
  41.                 trackingRect = [self addTrackingRect:NSMakeRect(0, 0, 128, 128) owner:self userData:nil assumeInside:YES];
  42.                 drawBezel = NO;
  43.         }
  44.        
  45.         [super setFrame:rect];
  46. }
  47.  
  48. - (void)mouseEntered:(NSEvent *)theEvent
  49. {
  50.         drawBezel = YES;
  51.         [self setNeedsDisplay:YES];
  52. }
  53.  
  54. - (void)mouseExited:(NSEvent *)theEvent
  55. {
  56.         drawBezel = NO;
  57.         [self setNeedsDisplay:YES];
  58. }
  59.  
  60. - (void)setSelected:(BOOL)flag
  61. {
  62.         selected = flag;
  63.         [self setNeedsDisplay:YES];
  64. }
  65.  
  66. - (BOOL)selected
  67. {
  68.         return selected;
  69. }
  70.  
  71. - (void)drawRect:(NSRect)rect
  72. {       
  73.         NSShadow *shadow = [[NSShadow alloc] init];
  74.         [shadow setShadowColor:[NSColor blackColor]];
  75.         [shadow setShadowBlurRadius:5.0];
  76.        
  77.         if (selected)
  78.         {
  79.                 [NSGraphicsContext saveGraphicsState];
  80.                
  81.                 [shadow set];
  82.                
  83.                 NSGradient *selectionRectGradient;
  84.                
  85.                 if (gender) //User is probably female – use yellow iPhoto style selection rect
  86.                         selectionRectGradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedRed:249/255.0 green:249/255.0 blue:119/255.0 alpha:1.0] endingColor:[NSColor colorWithCalibratedRed:242/255.0 green:174/255.0 blue:52/255.0 alpha:1.0]];
  87.                 else //User is probably male – use blue iTunes style selection rect
  88.                         selectionRectGradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedRed:118/255.0 green:164/255.0 blue:219/255.0 alpha:1.0] endingColor:[NSColor colorWithCalibratedRed:60/255.0 green:112/255.0 blue:182/255.0 alpha:1.0]];
  89.                
  90.                 NSBezierPath *selectionRect = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect(6, 6, [self frame].size.width – 12, [self frame].size.width – 12) xRadius:10 yRadius:10];
  91.                 [selectionRect fill];
  92.                
  93.                 [selectionRectGradient drawInBezierPath:selectionRect angle:-90.0];
  94.                
  95.                 [NSGraphicsContext restoreGraphicsState];
  96.         }
  97.        
  98.         NSImage *icon = [[NSImage alloc] initWithData:image];
  99.  
  100.         [NSGraphicsContext saveGraphicsState];
  101.         if (!selected)
  102.                 [shadow set];
  103.         [[NSBezierPath bezierPathWithRoundedRect:NSMakeRect(10, 10, [self frame].size.width – 20, [self frame].size.width – 20) xRadius:10 yRadius:10] fill];
  104.         [[NSBezierPath bezierPathWithRoundedRect:NSMakeRect(10, 10, [self frame].size.width – 20, [self frame].size.width – 20) xRadius:10 yRadius:10] addClip];
  105.         [icon drawInRect:NSMakeRect(10, 10, [self frame].size.width – 10, [self frame].size.height – 10) fromRect:NSMakeRect(([icon size].width – [self frame].size.width) / 2, ([icon size].height – [self frame].size.height) / 2, [self frame].size.width, [self frame].size.height) operation:NSCompositeSourceOver fraction:1.0];
  106.         [NSGraphicsContext restoreGraphicsState];
  107.        
  108.         NSMutableAttributedString *label = [[NSMutableAttributedString alloc] initWithString:couponDescription attributes:[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:[NSColor whiteColor], shadow, [NSFont boldSystemFontOfSize:10.0], nil] forKeys:[NSArray arrayWithObjects:NSForegroundColorAttributeName, NSShadowAttributeName, NSFontAttributeName, nil]]];
  109.         [label setAlignment:NSCenterTextAlignment range:NSMakeRange(0, [label length])];
  110.         [label drawInRect:NSMakeRect(5, 5, [self frame].size.width – 5, 20)];
  111.  
  112.        
  113.         if (drawBezel)
  114.         {
  115.                 NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect(0, 0, [self frame].size.width, [self frame].size.height) xRadius:10 yRadius:10];
  116.                 [[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.7] set];
  117.                 [path fill];
  118.         }
  119. }
  120.  
  121. @end

This is the view that actually copies the nice rounded rect display. It also allows you to display information on a mouseover bezel, if so desired.

As a semi-random aside, you’ll note that iPhoto and iTunes use different selection rects – iPhoto’s is a yellow gradient, while iTunes uses a blue gradient. I asked about 20 people, and found that preferences as to which one looks better divided exactly along gender lines, with females preferring iPhoto’s selection rect and males preferring iTunes’ selection rect. Mostly for entertainment purposes, I hacked up a little class that attempts to guess the user’s gender. ADCollectionViewItemView then uses this class to determine the user’s gender and draw the (presumably) preferred selection rect. Obviously, this isn’t necessary, though it is a nice touch. If you’d like to do the same thing, add ADGenderGuesser and its support files to your project; otherwise, pick whichever one of the NSGradients you prefer and draw it regardless of the user’s gender.

With all this in place, switch back to Interface Builder and set the classes of the Collection View Item and Collection View Item View to ADCollectionViewItem and ADCollectionViewItemView respectively. At this point, you should be good to build and go, resulting in something like this:

iPhoto Events View clone

Feel free to use any of the code in the post in commercial and noncommercial applications – all I ask is a credit in the About box.


It’s been a while since the last Selenium update, but I’m pleased to announce that a new version is finally here. Selenium 3.5 has been kicked out the door today, and it brings with it an assortment of bugfixes, features, and general coolness.

Though it’s never fun to admit it, Selenium, like essentially every application, has its fair share of bugs. Fortunately, I’ve taken care of some of the worst offenders in this update. Most important is a fix for an apparently random crasher when loading certain web pages – It took just one line of code to fix, but it should be gone for good as of this update. The URL suggestions that appear when you type in a new URL also had the tendency to occasionally get stuck, which can now be rectified by pressing Escape. The incidence of this has also been reduced – if it still happens to you, a specific description of where you clicked and what you typed would help in eliminating this problem completely. The final bugfix in this update involves disabling and enabling the back and forward arrows in the bibliography item editor sheet as appropriate. (eg when you’re editing the first or last item)

On a slightly nicer note, this update also brings some long-requested features. Key amongst these is the ability to drag and drop outline items to rearrange them. Even better, they now remember their order and whether or not they’re collapsed. Though it sounds incredibly simple, this took several weeks of fairly intense coding to bring about. It seems to work properly, but if you encounter any issues with it, please let me know. Selenium 3.5 also allows you to save projects as files. To do so, simply choose File > Save and save the selected project wherever you want. Additionally, Selenium now has support for bookmarklets. You can add them to Selenium’s bookmarks bar, and you can also invoke Selenium using a bookmarklet. To do so, simply add this link to your browser’s bookmarks bar. Clicking on it will launch Selenium and allow you to add the website you were viewing as a bookmark or a document.

Needless to say, there’s some other tweaks and features included in this update as well. Head on over to the Selenium page and check out the release notes while you download the update. If you haven’t tried Selenium before, now’s a great time to give it a shot. As always, if you have any questions or suggestions, don’t hesitate to email me.


Just a quick note to mention that I’ll be away from any form of Internet connection for the next week or so. Consequently, if you have questions or suggestions about Selenium, I may not be able to reply for a few days.


It’s been a while since I updated the Stuffed Iggy svn repository, so I thought I’d mention that I’ve added a few more things that may be of interest. The main one is the code behind the paginated text view added in Selenium 3.4.

Since NSTextView lacks the ability for you to do something like [textView setPaginated:YES], it’s necessary to do a bit of work to get a view that displays multiple separate pages of text and adds and removes them as necessary. Although the docs do cover the process, but they’re rather sparse and general.

The Texty project in the repository provides two classes, ADPaginatedTextView and ADPage, that you can drop in your project and have work more or less out of the box. Amongst other things, it provides support for single or side-by-side page viewing, margin indicators, and automatically flowing text between pages. Best of all, it looks fairly decent too:


Given the current track, it appears that tropical storm Hanna will be making landfall close to Stuffed Iggy Software around midnight tonight EST. Consequently, the store and this blog will go offline in the event of a power outage, which is fairly likely. Downloads and the Selenium product page should remain up, however. Depending upon the severity of the storm, I may be a bit late in responding to email as well. If you’re also in the path of Hanna or any other hurricane, best of luck!


This year, Stuffed Iggy Software is participating in Seth Dillingham’s annual software auction for the Pan-Mass Challenge. There’s 10 copies of Selenium up for grabs, along with a ton of other great Mac software. All the money raised goes to fund cancer research and treatment, so it’s a great opportunity to snag some awesome programs and help make a difference.

Though the auction itself hasn’t started yet, you can build your own application bundle and pick from more than 100 applications. Even better, you can suggest your own price – but please be generous, as all the proceeds are for charity. I’ll post again when the auction itself starts as well.


If you didn’t guess from the title, this will be a somewhat extensive post. Over the past couple days, cool several Selenium-related things have happened, and I figured I’d take this opportunity to highlight them.

First amongst the goings on is the release of Selenium 3.3.1. While not a very exciting release, it does fix a fairly nasty bug that would lock things up if you tried to do anything involving project names with the reset web browser option enabled. It also fixed a longstanding issue where the font from one project could be carried over to another, improved the behavior of the reset browser option, and added periodic autosaving to reduce the chance of Bad Things™ happening. If you haven’t already, download the update here.

On a somewhat more exciting note (at least for me), the folks over at TUAW gave a nice shoutout to Selenium today in a review of research tools for the Mac. I was particularly impressed to note Brett Terpstra’s use of the citation styles in combination with Markdown for blogging – when conceiving the feature, I never considered using it in such a manner. It does go to show just how flexible it can be, though. 

Next up is a quick hint about a semi-hidden feature in Selenium. As you may know, in almost any Cocoa application, you can press Escape (or F5 evidently, though I have it bound to Quicksilver) to bring up a list of text completion suggestions when typing. While this can certainly be a useful feature, Selenium carries it a bit further. Rather than presenting only a list of completion suggestions, Selenium will also offer internal citations for every source in your bibliography at the end of the list. You can then quickly arrow down and press Return to insert an internal citation anywhere in your writing. Currently, you can choose between three preset styles in the Word Processor preferences, though I may expand this in the future. It may not working perfectly 100% of the time, but it’s pretty close, and can be incredibly handy for citing works by authors with difficult-to-spell (or remember) names.

To finish, I’ll present you with the following image, hinting at things to (hopefully) come in the relatively near future. Don’t get your hopes up too much if you think you know what it is, but don’t be surprised to hear a few more details about something cool either.

redacted

It’s been a while, but a new version of Selenium is once again available. Version 3.3 adds a few things, the coolest of which is that you can now use your iSight to scan book barcodes to add them to the bibliography. This was made possible by the folks at Bruji’s excellent barcode scanning code, which made it amazingly simple to get this feature up and running in Selenium. I’ve put up a quick screencast that shows how it works.

The latest version of Selenium also includes a new feature called Open Quickly. It’s essentially like an internal version of Quicksilver, in that it allows you to work with Selenium almost entirely from the keyboard. Feedback on this would be greatly appreciated; I tried to include a wide variety of commands and some fairly sensible defaults, but I’d love to hear about any ideas you may have to improve it. The commands can be viewed (and changed) in the preferences.

One other change in 3.3 centers on the text and image snippets. The system for using them in previous versions was somewhat convoluted; you had to create a new snippet, and then copy the text or image you wanted to the snippet. To get the contents of the snippet back, you had to do the same thing in reverse. Now, you can simply drag and drop text and images to and from the text and image snippet lists, making them significantly faster and easier to use.

The final change in the update I want to call attention to is the user interface. You may have noticed that the browser controls are no longer in the toolbar, but are instead located under it. This change was made because the browser controls were often irrelevant and confusing when working with other things, and especially when annotating PDFs. Instead, the mode switcher bar that used to be located at the bottom of the window is now in the toolbar. As mode switching is relevant no matter what you may be doing, it fits well, and hopefully makes the modes more obvious to new users.

All that being said, if you have any ideas for improving Selenium, let me know. If you’re already using Selenium, you should be able to just check for updates and download the new version. If you’re not yet using Selenium, give it a try. And, of course, if you have any questions, don’t hesitate to contact support.


At some point, it’s going to be more difficult to keep them balanced than it would be to empty the bin. Until that time, more drinks and more coding.


At 10:00 this morning, the first Apple Store in South Carolina opened. Since Stuffed Iggy Software headquarters are just 20 minutes away, a trip downtown was pretty much required. There were about 50 people in line when I showed up at 8:00, which eventually grew to several hundred by opening time. Around 9:00, the employees started handing out water and getting the line to join in chanting and other assorted revelry. A few minutes before opening, the line was split into folks waiting for iPhones and everyone else, which seemed to help with expediting things for both groups. As I wasn’t picking up an iPhone, I got into the latter group, and we were let into the store right on time.

After high-fiving everybody in sight, and snagging a free shirt (woo!) I was able to get back to the Genius Bar to try to get a replacement power adaptor for my laptop. Unfortunately, as I didn’t have the serial number of the MacBook Pro, I had to go back home to get it. Though the store was slightly less packed when I returned, it was still crammed full of people. On the upside, I was able to get the replacement power adaptor without any problems in just a few minutes. All in all, it seemed quite well-planned, and it’s great to finally have an Apple Store nearby.

For pictures of the event and shirt unboxing, check out my web gallery.


« Previous Entries