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:

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:
-
// ADCollectionViewItem.h
-
-
#import
-
-
@interface ADCollectionViewItem : NSCollectionViewItem
-
{
-
}
-
-
@end
-
-
//ADCollectionViewItem.m
-
-
#import "ADCollectionViewItem.h"
-
-
@implementation ADCollectionViewItem
-
-
- (void)awakeFromNib
-
{
-
[[self view] bind:@"image" toObject:self withKeyPath:@"representedObject.image" options:nil];
-
[[self view] bind:@"imageDescription" toObject:self withKeyPath:@"representedObject.imageDescription" options:nil];
-
[[self view] bind:@"selected" toObject:self withKeyPath:@"selected" options:nil];
-
}
-
-
@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:
-
// ADCollectionViewItemView.h
-
-
#import <Cocoa/Cocoa.h>
-
#import "ADGenderGuesser.h"
-
-
@interface ADCollectionViewItemView : NSView
-
{
-
NSString *imageDescription;
-
NSData *image;
-
BOOL selected;
-
BOOL drawBezel;
-
BOOL gender;
-
-
int trackingRect;
-
}
-
-
-
@end
-
-
// ADCollectionViewItemView.m
-
-
#import "ADCollectionViewItemView.h"
-
-
@implementation ADCollectionViewItemView
-
-
@synthesize imageDescription;
-
@synthesize image;
-
-
- (void)setFrame:(NSRect)rect
-
{
-
if (trackingRect)
-
{
-
[self removeTrackingRect:trackingRect];
-
trackingRect = [self addTrackingRect:NSMakeRect(0, 0, [self frame].size.width, [self frame].size.height) owner:self userData:nil assumeInside:NO];
-
gender = [[[ADGenderGuesser alloc] init] gender];
-
}
-
else
-
{
-
trackingRect = [self addTrackingRect:NSMakeRect(0, 0, 128, 128) owner:self userData:nil assumeInside:YES];
-
drawBezel = NO;
-
}
-
-
[super setFrame:rect];
-
}
-
-
{
-
drawBezel = YES;
-
[self setNeedsDisplay:YES];
-
}
-
-
{
-
drawBezel = NO;
-
[self setNeedsDisplay:YES];
-
}
-
-
- (void)setSelected:(BOOL)flag
-
{
-
selected = flag;
-
[self setNeedsDisplay:YES];
-
}
-
-
- (BOOL)selected
-
{
-
return selected;
-
}
-
-
- (void)drawRect:(NSRect)rect
-
{
-
[shadow setShadowBlurRadius:5.0];
-
-
if (selected)
-
{
-
-
[shadow set];
-
-
NSGradient *selectionRectGradient;
-
-
if (gender) //User is probably female – use yellow iPhoto style selection rect
-
else //User is probably male – use blue iTunes style selection rect
-
-
NSBezierPath *selectionRect = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect(6, 6, [self frame].size.width – 12, [self frame].size.width – 12) xRadius:10 yRadius:10];
-
[selectionRect fill];
-
-
[selectionRectGradient drawInBezierPath:selectionRect angle:-90.0];
-
-
}
-
-
-
if (!selected)
-
[shadow set];
-
[[NSBezierPath bezierPathWithRoundedRect:NSMakeRect(10, 10, [self frame].size.width – 20, [self frame].size.width – 20) xRadius:10 yRadius:10] fill];
-
[[NSBezierPath bezierPathWithRoundedRect:NSMakeRect(10, 10, [self frame].size.width – 20, [self frame].size.width – 20) xRadius:10 yRadius:10] addClip];
-
[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];
-
-
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]]];
-
[label setAlignment:NSCenterTextAlignment range:NSMakeRange(0, [label length])];
-
[label drawInRect:NSMakeRect(5, 5, [self frame].size.width – 5, 20)];
-
-
-
if (drawBezel)
-
{
-
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect(0, 0, [self frame].size.width, [self frame].size.height) xRadius:10 yRadius:10];
-
[path fill];
-
}
-
}
-
-
@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:

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.

![[REDACTED] redacted](http://www.stuffediggysoftware.com/blog/wp-content/uploads/2008/08/redacted.png)
