macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Programming With Cocoa
Inside StYNCies

by Matthew Russell
03/11/2005

Stickies is one of the handiest little apps out there. It's been bundled with Apple's operating systems for ages, but Apple hasn't yet taken advantage of the new possibilities for interfacing it to the iPod and iDisk. Russotle, a budding software development company targeting the Mac, has recently filled these gaps by releasing StYNCies--a neat little utility that synchronizes your Stickies to your iPod and/or iDisk.

This first installment of a two-part series works through building a partial implementation of StYNCies and discusses some of the tricks programmers can use when working with iPods. The follow up article will feature part of Russotle's journey in reverse engineering the storage format of the StickiesDatabase file. Even if you don't own an iPod, have an iDisk, or like Stickies, this is still a fun series and is bound to teach you some new tricks. Having opened up and fiddled around in Xcode a few times is all the experience you need.

Fire up Xcode

Like the first part of any good series on Cocoa development, we're going to fire up our trusty companion Xcode. Get the latest edition for free from Apple Developer Connection. We'll follow the standard Model View Controller (MVC) paradigm for design, so take a moment to review if it's been a while.

Let's define the necessary classes in the interface first, so in Xcode create a new Cocoa Application project, name it StickiesSync, and save it wherever you like. From the main menu, choose New File... and then add an Objective-C class named AppController and agree to also add its header file as well. In the leftmost pane in Xcode, drag these files down under the Classes folder.

Repeat the same process to add another class, only this time, name the class IPodUtils (class names always start with a capital letter). Add in the interface details below to your AppController header, and take a moment to see where this is going. Given an interface with well-named methods like this one, you could probably fill in the implementation details on your own, given enough time. (Remember this, though; it's important for next time.)

@class IPodUtils;

@interface AppController : NSObject {
	
	IPodUtils* podUtil;
	NSFileManager* fileManager;
	NSStatusItem* statusItem;
	IBOutlet NSMenu *theMenu;
	
}

- (id)init;
- (void)dealloc;
- (void)awakeFromNib;

- (void)mountNotification:(NSNotification*)note;
- (void)synciPodsAndEject:(BOOL)eject;
- (void)copyStickiesToiPodAtPath:(NSString*)path 
	withPrefix:(NSString*)prefix
	toNotesFolder:(BOOL)toNotes;

- (IBAction)syncAndEject:(id)sender;

//"Quit" is hardwired through IB
@end

The first thing to notice is the existence of the standard methods: init, dealloc, and awakeFromNib. They'll do their usual thing. The method init allocates memory, dealloc cleans up allocated memory, and awakeFromNib handles setting up the display.

The method mountNotification: is almost self-describing; we'll use it to see if the mounted device in question is an iPod. The method synciPodsAndEject: handles syncing the StickiesDatabase file found at ~/Library/StickiesDatabase to the iPod, and conditionally ejects it. The method syncAndEject: has an Interface Builder (IB) Action macro as the return type and provides us with a spot to hook our menu selections from the user interface to the controller. Other than that, this header informs us of a class called IPodUtils that encapsulates some of the chores we perform with our iPod. We could have used a #include IPodUtils.h instead of @class IPodUtils; but it's not mandatory since none of its methods are directly referenced here. Including #include IPodUtils.h is required, however, in AppController.m--the implementation file.

In Interface Builder

With an interface defined for the controller, we now switch over to our work in Interface Builder (IB) before defining any more of the model. Expand the NIB Files folder in Xcode's left pane and double click on MainMenu.nib to open up IB. The first thing we need to do is something you may not have ever done before: click on the Window icon under the Instances tab of the main palette and press the delete key. This application is a faceless background app, so there's no main window.

In the controls palette, click the leftmost tab to reveal the Cocoa-Menus view. Drag a menu (the lower-right icon) over into the Instances tab of the main palette. The picture below shows what your main palette should look like once you're at this point. Once that's done, double-click on "Item1" of the new menu and change its text to "Sync Stickies and Eject." Change "Item2" to "Quit." Those are our two menu options.

creating the menu in Interface Builder After you've deleted the main window and dragged over a menu

Making sure to save AppController.h first, drag it from Xcode's left pane and drop it onto the main palette in IB beside the menu you just added. IB should switch to the Classes tab, and you'll see AppController as a subclass of NSObject. With AppController selected, choose to Instantiate AppController from IB's Classes menu.

Back in the Instances tab, you'll notice a blue cube that represents the controller. While holding down the Ctrl key, click on the blue cube and drag your mouse over to NSMenu and release it. On the dialog box that appears, choose to Connect the AppController's theMenu outlet to NSMenu. This connection gives our controller a means of referencing the NSMenu. If you recall, the NSMenu was defined using the IBOutlet macro in AppController.h.

control-dragging in Interface Builder You'll get a markup when you control drag from objects in IB

The Quit action on the menu can be set in IB without any code at all. Using the same Ctrl-drag method, drag from the Quit option on the menu over to File's Owner in the Instances tab of IB's main palette. Choose the terminate: action once you've released your mouse button and click on the Connect button.

The final step in IB is to specify an action for the menu item Sync Stickies and Eject. Ctrl-drag like before, only this time drag to AppController in IB's main palette. Choose the action syncAndEject: in the info window and click Connect. We have syncAndEject: as an option because our design back in AppController.h declared it using the IBAction macro. Close IB and take a water break. We're off to implementation.

Meanwhile, Back in Xcode

The first order of business back in Xcode is to define bodies for init, dealloc, and awakeFromNib.

//Don't forget the appropriate include
#import "IPodUtils.h"

- (id)init 
{
	if (self = [super init]) 
	{
		fileManager =  [[NSFileManager alloc] init];
		podUtil	= [[IPodUtils alloc] init];
		
		NSNotificationCenter *nc;
		nc = [[NSWorkspace sharedWorkspace] 
			notificationCenter];
		[nc addObserver:self
			selector:@selector(mountNotification:)
			name:NSWorkspaceDidMountNotification
			object:nil];	
	}
	return self;
}

/*dealloc for AppController */
- (void)dealloc
{
	NSNotificationCenter* nc;
	nc = [NSNotificationCenter defaultCenter];
	[nc removeObserver:self];
	
	[statusItem release];
	[podUtil dealloc];
	[fileManager dealloc];
	[super dealloc];
} 

- (void)awakeFromNib 
{
	statusItem = [[[NSStatusBar systemStatusBar] 
		statusItemWithLength:NSSquareStatusItemLength] 
		retain];
	[statusItem setHighlightMode:YES];
	[statusItem setTitle:
		[NSString stringWithFormat:@"%C", 0x25B0]];
	[statusItem setMenu:theMenu];
	[statusItem setEnabled:YES];
	
}

The init method is interesting because it introduces the NSNotificationCenter, which is the standard mechanism to determine when a device (iPod anyone?) is mounted. It's a pretty cool concept, because it allows applications that are interested in system-wide events to know about them and respond accordingly.

NSNotificationCenter's addObserver: method is straightforward. It says to trigger AppController's method mountNotification: each time a device mounts. In our mountNotification (defined later), a mechanism determines if the device that mounted is an iPod and acts accordingly. In dealloc, the memory allocated in init is cleaned up, which is standard procedure.

In awakeFromNib, something you don't see every day happens. An item for the status bar is created through the NSStatusBar class, a title is set for it, and a menu is connected. The title of the status bar item is simply a little black parallelogram, whose Unicode value is 0x25B0. This character is pretty boring, so here's how you can be creative and use an image of your own devising: in GIMP or your favorite paint program, create an image and save it as a .png file. Drag it into the Xcode's left pane, and choose to copy it into the project when prompted. In Xcode, drag it down into Bundle Resources under the StickiesSync target in Xcode. Placing it there bundles it with your app and you can then programmatically refer to it as your title (instead of the Unicode character) with this snippet:

NSImage *image = [[NSImage alloc] initWithContentsOfFile:
		[[NSBundle mainBundle] pathForResource:@"yourImage"
                                            ofType:@"png"]];
[statusItem setImage:image];
Cocoa in a Nutshell

Related Reading

Cocoa in a Nutshell
A Desktop Quick Reference
By Michael Beam, James Duncan Davidson

Pages: 1, 2

Next Pagearrow