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.)

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.

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.

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.

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:

Hide the Application Menu

Although our project isn't complete, now is a good time for a gut check, so tell Xcode to Build and Go. You should not have any compiler errors, but definitely will have some warnings because there's a lot of method bodies not defined yet. You should be able to run your application, see the symbol up on your status bar and successfully quit the application using the symbol's drop-down menu.

You'll get a runtime message that the syncAndEject: action couldn't be connected, but we already know this. The point is to get our bearings and take care of any typos that have occurred up 'til now. But wait a minute--there's still a full menu when we run the app. One other thing we can knock out right now is backgrounding the app to remove that menu bar. Double-click on Info.plist in the Resources folder of Xcode's left pane to open up the plist editor. Expand Root and click on any of the items in the dictionary. Click on New Sibling up top and add the key "LSUIElement" (Launch Services User Interface Element) with a value of 1. If you don't have Xcode set up to edit plists with the Property List Editor, you can change this to your default in Xcode's File Types preferences, open the Property List Editor in your /Developer/Applications/Utilities folder, or just add in this plain text to the dictionary by viewing it as plain text.

<key>LSUIElement</key>
<string>1</string>

Save the changes, and choose Build and Go in Xcode. Your menu problems have just disappeared--literally.

Finishing off AppController

Let's finish off AppController. Once that's done, all that remains for this installment is to define the IPodUtils class. Check out the method bodies below and copy them into your AppController.m file.

You've noticed that syncAndEject: is simply a wrapper that calls synciPodsAndEject: with an argument of YES. Our finished application will sync the iPod each time it's connected to the computer, as well as any time the user chooses to sync it from the menu bar. Passing the buck to synciPodsAndEject: allows the reuse of identical code that's also needed in the method mountNotification, as will be seen. The method mountNotification retrieves the path of the recently mounted device and passes it to our iPod utility class (still undefined at the moment) to determine if the device is an iPod, and conditionally calls synciPodsAndEject:.

In synciPodsAndEject: we find most of the action. In short, the iPod utility class returns to our controller an array of paths that correspond to mounted iPods, and is able to determine if the device is older than a 3G iPod. If it is older than 3G, the notes are copied into its Contacts folder using the vCard format the iPod expects to read via a call to copyStickiesToiPodAtPath:.

A common file prefix is added to each note copied in order to group the notes together. The Contacts folder orders items alphabetically, so if notes don't share a common prefix, they get scattered throughout your contacts, which is not very convenient. If the iPod is 3G or newer, the notes can be copied into the Notes folder as plain text and ordered alphabetically. Each sync is accomplished by removing any existing notes on the iPod and then copying in the most recent ones from the Stickies application. The implementation details of reverse engineering the database Stickies uses is left until next time.

Creating iPod Utilities

Before this installment finishes, let's fill in the IPodUtils class. There's no rocket science involved here; it's just some grunt work that's convenient to encapsulate into a easier-to-use class. The interface for class IPodUtils is like so:

You should be able to see how these methods fit into everything based on their method names. The only "clever" thing we do here is take advantage of some things we know about all iPods: 1) they all have an iPod_Control directory and 2) 3G and new iPods have a Notes folder, while older models do not. These two details allow us to determine if a device in question is indeed an iPod and how we should go about storing text notes into it. Here are the implementation details:

If you remember to add a #import "IPodUtils.h" directive to the top of your AppController.h file, you should be able to compile with no errors. Your user interface is now complete. You should run your app and verify that it is working. If you have an iPod connected and choose Sync Stickies and Eject, you should see the output in the Run Log console, and your iPod should eject. Once that's done, redock it to see that your app detects it and issues the statement again to the console.

At this point, all that's left is to pull data out of the StickiesDatabase file located in ~/Library and send it over to the iPod. But wait, Apple hasn't published any API for Stickies. Reverse engineering, anyone?

You'll see that a little intuition and clever guessing allows us to develop our own API without significant pains. If you want to get ahead, take a peek at the documentation on NSUnarchiver before next time. If you like where this is going, you can also download StYNCies, a more sophisticated version of the app we're building.

Matthew Russell
is a computer scientist from middle Tennessee; and serves Digital Reasoning Systems as the Director of Advanced Technology. Hacking and writing are two activities essential to his renaissance man regimen.