bdunagan

fill the void - bdunagan

25 Sep 2009
Syncing Arrows in iTunes

When I sync in my iPhone, I love the syncing arrows in the source list. They smoothly turn around and around, letting me know that iTunes is busy syncing all of my data. iPhoto shows the same rotating arrows when it syncs a MobileMe album. Unfortunately, those arrows aren't an NSProgressIndicator Cocoa object; those objects only display the spinning lines for isIndeterminate:YES (see my NSProgressIndicator example from last year). Instead, that nice UI feedback is created through home-brewed animation.

On an side note, comparing the Resources folders in iTunes and iPhoto gives some insight into Apple's approach with each product. (Right-click on any application and click "Show Package Contents" to navigate to the Resources folder.) iTunes' Resources folder holds a mere 118 items, including 19 language folders. Only 118! There are extremely few icons in the folder, implying that iTunes draws almost all of them in code. On the other hand, iPhoto has 1,532 items in its Resources folder. Seemingly every state of every icon in the entire photo application comes from an icon in this folder. Different approaches. The end result is iTunes weighing in at 150MB and iPhoto at 410MB. Perhaps the stark differences are market-driven, as iTunes is predominately delivered online whereas iPhoto is typically pre-installed or comes on a DVD. For this tutorial, I'm going with iPhoto's approach.

Let's make syncing arrows for a sample Mac application. Originally, I thought I should use Core Animation, as it's in 10.5+. However, at WWDC, an Apple Core Animation engineer told me that only the iPhone apps can rotate table cells like that, as only UITableViewCells subclass a view object (UIView). On the Mac, NSCell subclasses NSObject directly, so he said to go with a simple NSTimer and cycle through a set of images. So, that's what I did here.

I want a simple application for this demo: just a list (NSTableView). Inside each row, I want those syncing arrows, rotating at varying speeds to see which interval is the optimal. There are two parts to this UI: an NSTimer and a set of icons in my cell class. To make the code very simply, I leverage Cocoa Bindings to trigger a UI refresh, instead of calling NSView::setNeedsDisplay. The screenshot below shows how to connect the NSArrayController in Interface Builder to the two NSTableColumns in the NSTableView.

The icons are straight out of iPhoto. Go into iPhoto.app/Contents/Resources and search for "sl-status_syncanimation-01.tiff". There are six total.

First, I initialize the cell's state, including the timer and the icon array.

- (id) initWithInterval:(NSTimeInterval)interval {
	self = [super init];
	if (self != nil) {
		// Set item's title.
		self.title = [NSString stringWithFormat:@"Item with interval %1.2fs", interval];
		// Set up status array.
		icons = [[NSArray alloc] initWithObjects:
				 [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"syncing_arrows1" ofType:@"png"]],
				 [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"syncing_arrows2" ofType:@"png"]],
				 [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"syncing_arrows3" ofType:@"png"]],
				 [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"syncing_arrows4" ofType:@"png"]],
				 [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"syncing_arrows5" ofType:@"png"]],
				 [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"syncing_arrows6" ofType:@"png"]],
				 nil];
		// Initialize icon state.
		icon = [icons objectAtIndex:0];
		statusState = 0;
		// Set up status timer.
		stateTimer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(updateStatus) userInfo:nil repeats:YES];
	}
	return self;
}

Next, I simply add the method called by the NSTimer. In there, I update the status icon and increment to the next state.

- (void)updateStatus {
	self.icon = [icons objectAtIndex:statusState];
	statusState = (statusState + 1) % [icons count];
}

I posted the code on Google Code as SyncingArrows. Keep in mind that I wrote it in Leopard (10.5) using Xcode 3.1.2; let me know if you have issues trying it.

Previous LinkedIn Twitter GitHub Email Next