Apple Developer Connection
Advanced Search
Member Login Log In | Not a Member? Support

Creating an Application with Tiger Technologies

Individually, the new technologies in Mac OS X v10.4 Tiger—Spotlight, Core Data, Core Image, Dashboard, Sync Services, Automator, and many more—present developers with a rich toolset with which to build Mac OS X applications. In fact, Tiger adds more new technologies for developers to use than any release since version 10.0 was released in 2001. Taken together, the technologies in Tiger take the platform to a whole new level. They open up possibilities for entirely new kinds of application development and change the way that you can approach developing Mac applications.

It's easy enough to focus in on the details of each of Tiger's new technologies. We've done so in the articles in the Tiger Developer Overview Series. But focusing on each technology individually only goes so far. The real magic in an application built for Tiger comes from how the technologies come together and how they can build on, and complement, each other. An application built for Tiger will use many of Tiger's unique technologies together and synergistically. In fact, if you were to create a cross-section representation of an application that takes full advantage of Tiger, you could make something like the following diagram:

downloadable .dmg file with sample app

As you can see, there are many ways in which the various technologies in Tiger can be used together. And, if an application provides a Dashboard widget or Automator Actions, the application's functionality is no longer constrained to a single process, but is spread out over many. For example, an application can use Core Data to keep its data on disk and a Dashboard widget can use that same data store in a read-only mode to present at-a-glance data to a user.

Using Cocoa Makes It Easy

To illustrate how to take advantage of the new technologies in Tiger, we're going to do something a bit different and present the creation of a prototypical Cocoa application over a series of articles. This first article in this series covers the first few steps of creating our application, including putting together a data model and providing a user-interface. As we build up the application, we'll look at most of the new technologies in Tiger and how they can be utilized. When we're done, we'll have covered the spectrum of technologies that you should consider using in your own applications.

Because we can't just sit down with you and your application and start adding Tiger technologies to it, we're going to start from scratch with a prototype that we can use to communicate our points. For this, we've picked a problem area that is generic enough to be familiar with every reader: that of managing to-do items. Our application will be small enough that we can focus on the points at hand rather than covering too much in the way of application specific code.

We're going to call our application, simply enough, To-Do. And, not only are we going to describe it as we go, for each article we're going to provide a download so that you can look at the project for yourself to examine the details, if you are interested.

This series will by no means attempt to be a "best-practices" document—our aim is simply to inspire you to think about Tiger application development in a new way. Whether you are building a new application from scratch or reworking an existing application from the ground up, you'll want to consider the approaches and ideas that we'll be covering and evaluate how you can apply them in your own development.

Since we're starting from scratch, we're going to build a Cocoa application. Cocoa is the premier application framework on Mac OS X and its features and tools make creating our sample application easy. Concerning our starting point, we could begin by laying out the user interface in Interface Builder. There is, however, a much more appropriate place to start for our purposes. Instead of laying out the View (in Model-View-Controller pattern terms), we're going to start out with the Model.

Designing the Data Model

The To-Do application's model is a simple set of to-do items. In Core Data parlance, the model will be a set of ToDoItem entities. This entity has several pieces of information, called attributes, associated with it that a user can define and which should be captured in the model. These are:

  • A title or name that gives a short and clear indication of the task to be performed.
  • A due date by which the task should be completed.
  • Some additional free-form text that allows extra information relevant to the to-do item to be stored.
  • A status flag indicating whether the to-item is pending or complete.

There are many other attributes that we could possibly associate with a ToDoItem entity, such as the priority of a task and responsible parties. Indeed, many other applications that model to-do items do make these attributes part of their model. However, we are going to be careful not to try to model too much up front in our application. Simply adding attributes won't necessarily help our customers use the application. After all, isn't everything a high priority? It's hard enough for a user to determine what a to-do item is and by when it should be performed. Making a model too complex can turn users off. Instead, we're going to focus on other ways to let users sort though their to-do items that we'll add later.

Even though we don't want to expose too much to the user, there are a few more things that we should capture about each to-do item for our application's internal use. These are:

  • The date on which a to-do item was created.
  • The date on which a to-do item was marked as completed.

These bits of information are latent data. It's data that can be placed into the model by our application, but which a user doesn't directly add. We can use this to later add informative messages to the user, such as, "It's been 43 days since you created this item." When designing your own data models, you should look for places where you can pick up interesting data that can be useful but which isn't directly entered in by the user.

Implementing the Data Model

Implementing the Data Model

Now that we know what the data model is going to look like, let's implement it. The first thing to do is to launch Xcode 2.0 and create a new project. For our application, we're using the Core-Data Application project template. This gives us a single window application and creates an empty Core Data model that will store its data in the ~/Library/Application Support/ folder.

Once created, we see that our project has been set up to link against the Cocoa framework, which includes the AppKit, Foundation, and Core Data frameworks. We also have a nib file for the user interface, an application delegate class, and a data model file, To-Do_DataModel.xcdatamodel. Double-clicking on the data model file opens it up in Xcode's Data Modeling tool. This graphical tool lets us put together the data model we designed for the To-Do application.

Diplsaying ToDo Item Entity

As we discussed, our model is relatively simple and requires only one entity: ToDoItem. Create this entity using either the Design > Data Model > Add Entity menu or the (+) button under the Entity list view in the design view. To this entity, we define the following attributes:

  • title, a required String attribute with a default value of "New To Do Item".
  • note, an optional String attribute with no default value.
  • completed, a required Boolean attribute with a default value of No.
  • dateDue, an optional Date attribute with a default value of Today.
  • dateCreated, a required Date attribute with a default value of Now.
  • dateCompleted, an optional Date attribute.

Once the attributes have been defined, they show up in the modeling tool Inspector as follows:

in the modeling tool inspector

This is obviously a very simple data model, but our application has simple needs. Now that we've defined this model, we can let CoreData handle the rest of the details of managing the to-do items and persisting them to disk. We also will be able to easily hook this model up into our user interface and take advantage of automatic undo and redo.

Test Driving the Model

At this point, we'd like to build and run the application to see what we've done. But since we started with the model, we don't have a user interface yet, right? Well, that's true, but we can fix that very quickly by opening up the MainMenu.nib file and option-dragging the ToDoItem entity from Xcode onto our application's window.

Inspector adds manage multiple objects

When we do this, Interface Builder responds with a dialog that asks us whether we want to manage one item or multiple items of our model object. Since we're going to have multiple to-do items, we answer that we want to manage multiple objects. Interface Builder then creates an interface that exposes all of the attributes of the ToDoItem entity and provides a table view so that we can manage a set of them. It even provides a search box so that we can focus the table view on just a few items—a great user feature that we'll implement in our finished user interface.

Now, when we build and run the application, we can create some to-do items and work with the model. In essence, we're test driving the model before writing the real user interface. We can also see how Interface Builder hooks into our model by using the Inspector to see the bindings settings for the various user interface components, as shown in the figure to the right.

This obviously isn't a great interface to ship to our users, but it lets us experiment with the model under the application and make sure that we are capturing the correct data. We can even quit and relaunch the application and the data persists to the ~/Library/Application Support/To_Do/To_Do.xml file, thanks to Core Data. Note that this location isn't automatic to Core Data, instead it is set by the code in the To_DoAppDelegate.m file.

Now that we have a working data model and we're pretty happy with the way it works, it's time to move on and create a user interface that we'd like our users to use.

Creating a User Interface

hand sketch of interface

Even though our To-Do application is simple, we still want it to have an effective interface. And, as tempting as it is to use Interface Builder and drag out elements, sometimes the best approach is to start with a low-tech solution: pen and paper. Or maybe even a whiteboard. After sketching out several possibilities and talking through them with trusted colleagues, we ended up with the sketch to the right. The interface has a single main window and an auxiliary panel that will let the user edit some of the details of each to-do item. Like the rest of what we've done so far, it's not very complex, but that's fine.

Once we have settled on our interface, its tempting to delete the auto-generated one that we created using the ToDoItem entity and start from scratch. But, the auto-generated user interface contains some really valuable information that we can use when building up the real interface: the binding settings to hook the various user interface elements to the underlying model. So, instead of deleting the auto-generated interface, we built up the new interface in a separate window. When we've got things to the point where we're happy, we will delete the first window with the auto-generated interface.

Here are the general steps we followed to build up the interface, referring to the pre-built interface's bindings settings as needed:

  1. Created a new window to hold the to-do item list and connected its delegate outlet to the To_DoAppDelegate object in the Interface Builder instance palette.
  2. Added a table view with two columns to the new window. We set the first column up to use NSButtonCells. The second column we labeled as "To Do Items".
  3. Bound the checkbox column in the table to To Do Item Array Controller's arrangedObjects.completed keypath.
  4. Bound the To Do Items column in the table to the To Do Item Array Controller's arrangedObjects.title keypath.
  5. Added a search field to the user interface and bound its Search predicate to the To Do Item Array Controller's filterPredicate key with a display name of Title and predicate format to title contains[c] $value. This allows the to-do list to be constrained to just the titles that contain the text typed into the search box.
  6. Created an inspector panel.
  7. Created a text label and bound its value to To Do Item Array Controller's selection.title keypath.
  8. Created a date picker and bound its value to To Do Item Array Controller's selection.dateDue keypath.
  9. Created a text field and bound its value to To Do Item Array Controller's selection.note keypath.
  10. Connected the File > New menu item to the To Do Item Array Controller's add: action. This allows the user to create new to-do items using the standard Command-N keystroke.
user interface window and panel

Now, we have a completely new interface for the To-Do application. Better yet, it's fully functional. You can add items, edit the notes associated with an item, and even change the due date. Even better, the search field works. You can type "Wat" into it and the list will change to show just the "Water Plants" entry. As well, our strategy for keeping the pre-generated has the side effect that even if we don't get something right in our new interface, we can still examine the data using the old one.

There's also something going on here that you may have noticed. We haven't written a line of code. That's because we've been able to leverage the power of Core Data and Cocoa bindings. In essence, we've taken advantage of Cocoa components for all three layers of the Model-View-Controller pattern. The Model is provided by the Core Data layer. The Controller is provided by the Cocoa bindings layer. And the View is provided by Interface Builder. We're not opposed to writing code, but when the tools at hand provide the functionality needed, there's no reason to do so.

However, there's one bit of code that does need to be written to fulfill the contract of the data model we designed. When the completed checkbox of a to-do item is checked, we want to update the dateCompleted attribute of the ToDoItem entity. As well, if the user unchecks the completed checkbox, we want the dateCompleted. To do this, we created the following NSManagedObject subclass for the ToDoItem entity with a custom accessor method for the completed attribute:

- (void) setCompleted:(BOOL)completed
{
    [self willChangeValueForKey:@"completed"];
    [self setPrimitiveValue:[NSNumber numberWithBool:completed] forKey:@"completed"];
    if (completed) {
        [self setValue:[NSDate date] forKey:@"dateCompleted"];
    } else {
        [self setValue:nil forKey:@"dateCompleted"];
    }
    [self didChangeValueForKey:@"completed"];
}

This code checks to see if the completed attribute is being set to YES or NO, and sets the dateCompleted attribute appropriately. It sets the dateCompleted property by using Key-Value-Coding methods, which are extensively used by Core Data. As well, it properly calls the willChangeValueForKey: and didChangeValueForKey: methods which are used to notify the rest of the Core Data Managed Object Model that the entity has been updated.

At this point, we've created a basic, fully-functional application that we'll use in the next few articles, and to which we'll then add more Tiger features.

Download the Project

To help you better pick apart what we've done here, you can download the project as it stands as a compressed disk image file. The project state is exactly at the point where the interface has been created, complete with the auto-generated window showing you the raw view into the data model.

Conclusion

In this first article in the series, we've built a simple application that takes advantage of Core Data, a technology new in Tiger, as well as Cocoa bindings, which were introduced in Mac OS X Panther. We've laid the foundation for the next few articles where we'll hook up our application to Sync Services, Spotlight, Dashboard, Automator, and more.

For More Information

Posted: 2005-06-06