Cocoa for Scientists (Part XXIII): iTunes-ifying a Core Data App
Author: Drew McCormack
Web Site: www.maccoremac.com
In the last installment of our foray into Core Data, we set up a simple Core Data model in the Xcode modeler. Today, we are going to add an iTunes-like interface to our molecular database program. At the end of this lesson, having written less than 250 lines of code, you will have a working app that
- Imports molecular coordinates from XYZ files;
- Displays the coordinates in an iTunes-like interface.
- Stores its data in an SQLite database, for fast, non-sequential access, and scaling to gigabytes;
- Supports unlimited undo/redo; and
- Includes filtering of molecules via a search box.
To whet your appetite, there is a screenshot of the completed app below, and you can also download it.

The Views
In order to cut down of the length of this tutorial, we won’t go into too much detail about how you put together the views in Interface Builder. It’s mostly fairly standard, and doesn’t have too much to do with Core Data.
The objective is to end up with a window in MainMenu.nib that looks a bit like the following screenshot.

Here are a few hints to help you out:
- All of the views can be found in the Library (on Leopard). If you can’t see the Library, bring it to the front by choosing Tools > Library.
- To find a view in the library, enter its name in the search field at the bottom.
- The interface consists of: 2
NSTableView’s, 1NSSplitView, 2NSButton’s (Rounded and Textured); and 1NSSearchField. - The two table views are subviews of the split view. To achieve this, select the two table views first, and then choose Layout > Embed Objects In > Split View.
- The images on the two buttons are standard template images in Leopard. In the Button Attributes pane of the Inspector, choose NSAddTemplate and NSRemoveTemplate from the Image combo box.
- The table view on the right has 4 columns; the last 3 should only accept decimal numbers, so they have an
NSNumberFormatterassociated with them. You can find this in the Library — it has a dollar symbol as icon — and drag it into each column. In the Inspector, choose Decimal as the formatter style.
We’ve skipped over a lot of the fine points, but hopefully you can cobble together the basic elements of the interface, even if it is not quite as refined as what is presented above. You can also open the nib file in the downloaded project to see how the views have been configured to look and behave as they do.
Bindings and Controllers
With the views in place, it is time to connect them up to the data models we added last week. For this, we’ll need some controller objects.
Locate the NSArrayController class in the Library in Interface Builder, and drag it out twice to form two instances in the MainMenu.nib window. Name one of them ‘Molecules Controller’, and the other ‘Atoms in Molecule’.
Select the Molecules Controller instance, and set it up so that the Inspector window looks like this.

Three things are important here:
- The Prepares Content checkbox should be selected, so that the controller automatically loads the core data objects.
- The Mode should be set to Entity, and Molecule filled in. This controller will control a collection of Molecule entity objects.
- The controller should be ‘Editable’, because we want to be able to add and remove molecules.
The Molecules Controller will work with the Molecule objects from Core Data. To configure this, select the controller, and open the Bindings tab of the Inspector (4th from left). In the Parameters section at the bottom, open the Managed Object Context binding and bind it to Molecular_Core_AppDelegate with the Model Key Path ‘managedObjectContext’. We will discuss the managed object context more below, but for now you can think of it as a container for all the Core Data objects.
To bind the molecules table view to the Molecules Controller, click on it until the Inspector shows that the Table Column is selected. Then go to the Table Column Bindings tab of the Inspector, and configure the Value bindings as shown below.

The table on the right of the main window will contain a list of atoms for the molecule selected in the table on the left. To achieve this end, we’ll bind the atoms table to the Atoms in Molecule controller, and the Atoms in Molecule controller to the selection of the Molecules Controller.
First, select the Atoms in Molecule controller, and in the Bindings tab of the Inspector
- Bind the Managed Object Context to the Molecular_Core_AppDelegate instance, with the Model Key Path ‘managedObjectContext’, just as for the Molecules Controller.
- Bind the Content Set to the Molecules Controller, with Controller Key set to ‘selection’, and Model Key Path set to ‘atoms’.
With the Atoms in Molecule controller setup, we can now bind the atoms table view to it. Click on each column of the table view in turn, and setup the bindings that follow. (You will need to click several times to dig down and select each table column.)
| Column | Binding | Bind to | Controller Key | Model Key Path |
|---|---|---|---|---|
| Element | Value | Atoms in Molecule | arrangedObjects | element.symbol |
| X | Value | Atoms in Molecule | arrangedObjects | positionX |
| Y | Value | Atoms in Molecule | arrangedObjects | positionY |
| Z | Value | Atoms in Molecule | arrangedObjects | positionZ |
Setting up the buttons and search field will be left until a bit later, after we have written some action methods to connect them to.
Classes vs Entities
Much of the view has now been put in place, but we still have no way of importing data. We need a bit of controller code, and some model objects to get the ball rolling. We are going to start by creating classes that correspond to the Core Data entities that were declared last time in our model. This is not compulsory — if you don’t associate an entity with a class, it will simply use the class NSManagedObject — but more often than not you will want to do it. (NSManagedObject is a subclass of NSObject that is the base of all Core Data entities.)
In this case, we will name our classes exactly the same as the corresponding entities, though this is not a requirement. In Xcode, create three new Objective-C classes called Atom, Molecule, and Element by choosing File > New File…. Edit each header file so that they inherit from NSManagedObject, rather than the default NSObject. Now click on the Core Data model from last time, and select each entity in turn; for each, fill in the appropriate class name in the Class field, in place of NSManagedObject.
Properties and Garbage Collection
Before writing the source code of these three managed object classes, we first need to make a change to the project settings. It’s not necessary to use garbage collection when working with Core Data, but it can significantly reduce the amount you have to type, so we’ll use it for Molecular Core. Double click the project icon at the root of the Files & Groups source list, and navigate to the Build tab. Locate the GCC 4.0 - Code Generation section, and for Objective-C Garbage Collection choose ‘Required’.
Now we can start entering some code. We’ll begin with the Atom class, which is very simple. The header looks like this:
#import <Cocoa/Cocoa.h>
@class Element;
@class Molecule;
@interface Atom : NSManagedObject {
}
@property (readwrite, retain) Element *element;
@property (readwrite, retain) Molecule *molecule;
@property (readwrite, copy) NSNumber *positionX, *positionY, *positionZ;
@end
and the implementation is no more complex
#import "Atom.h"
@implementation Atom
@dynamic element;
@dynamic molecule;
@dynamic positionX, positionY, positionZ;
@end
These lines simply declare a number of properties, which are a new feature in Objective-C 2.0. You’ll notice that these properties are actually the same as the attributes in the Atom entity. We could use the Atom entity without these properties, in which case we would call setValue:forKey: and valueForKey: to set and get the attributes, respectively. But introducing properties for the entity attributes as above allows the dot syntax to be used, which — as we will see shortly — can make your code more readable.
To declare properties for entity attributes, you need to use the @dynamic keyword rather than @synthesize, to indicate that the accessor methods will be added dynamically by Core Data, and do not need to be generated by the compiler.
Fetch Requests and Managed Object Contexts
The Element class declares a property for an entity attribute, but it also includes a method
#import <Cocoa/Cocoa.h>
@interface Element : NSManagedObject {
}
@property (readwrite, copy) NSString *symbol;
+(Element *)elementWithSymbol:(NSString *)symbol inManagedObjectContext:(NSManagedObjectContext *)context;
@end
The elementWithSymbol:inManagedObjectContext: class method first checks to see if an Element already exists with the symbol passed, returning it if it does, and creating a new Element if one doesn’t exist.
#import "Element.h"
@implementation Element
@dynamic symbol;
+(Element *)elementWithSymbol:(NSString *)symbol inManagedObjectContext:(NSManagedObjectContext *)context {
// First try to fetch an existing element with the symbol passed
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Element"
inManagedObjectContext:context];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"symbol == %@", symbol];
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
fetch.entity = entity;
fetch.predicate = predicate;
// Execute the fetch, and get the resulting element from the array
NSArray *elements = [context executeFetchRequest:fetch error:nil];
Element *element = [elements lastObject];
// If no existing element found, create a new one
if ( nil == element ) {
element = [NSEntityDescription insertNewObjectForEntityForName:@"Element"
inManagedObjectContext:context];
element.symbol = symbol;
}
return element;
}
@end
There is a lot of Core Data goodness in this code — the techniques shown are very common. The first part of the elementWithSymbol:inManagedObjectContext: performs what is known as a fetch request. This is a little bit like searching for files with Spotlight, but you are searching for managed objects that meet a given criterion. In this case, we are searching for any managed objects belonging to the Element entity, for which the symbol attribute is the same as the string passed to the method. A NSFetchRequest is used for this purpose; it needs to be told details of the entity in question — which are stored in an NSEntityDescription — and passed an NSPredicate that is used to filter the fetched objects. A predicate is a boolean valued expression with its own special syntax; this syntax is described in the Predicate Programming Guide.
The last class involved in the fetch request is NSManagedObjectContext. This is a very important class in Core Data. You can think of it as a container that holds your managed objects. Whenever you create a new managed object, you have to create it in a managed object context. And if you want to execute a fetch request, you have to send the executeFetchRequest:error message to a context, as above. This method returns an array of managed objects for which the predicate evaluates to true. In this particular case, we assume at most one object is returned, and retrieve it with the array method lastObject.
Creating New Managed Objects
If it turns out there is no Element that has the symbol passed, a new object must be created. The way you create a new managed object is very different to the way you are used to creating objects, with init or a variation thereof. For NSManagedObject and subclasses, you actually call a method of NSEntityDescription in order to create a new instance: insertNewObjectForEntityForName:inManagedObjectContext:. (The object returned is autoreleased, though this is not relevant in the garbage-collected code we are working with.) With a new Element created, you can set its attributes. In the code above, the dot notation is used to set the symbol attribute of the Element entity.
The source code for the Molecule class is the most extensive, but most of it has little to do with Core Data. The header file declares two methods: the first, moleculeFromCSVString:withName:inManagedObjectContext: is factory method for creating new instances, and the second addAtomWithPosition:andSymbol: is an instance method that creates a new Atom instance and adds it to the Molecule.
#import <Cocoa/Cocoa.h>
@interface Molecule : NSManagedObject {
}
@property (readwrite, copy) NSString *name;
+(Molecule *)moleculeFromCSVString:(NSString *)csvString withName:(NSString *)name
inManagedObjectContext:(NSManagedObjectContext *)context;
-(id)addAtomWithPosition:(double *)position andSymbol:(NSString *)string;
@end
moleculeFromCSVString:withName:inManagedObjectContext: parses a string in XYZ format and extracts the positions and symbols of the atoms it contains. (Update: I mistakenly named the method after CSV format, but it should be XYZ.)
+(Molecule *)moleculeFromCSVString:(NSString *)csvString withName:(NSString *)name
inManagedObjectContext:(NSManagedObjectContext *)context {
// Create new molecule
Molecule *molecule = [NSEntityDescription insertNewObjectForEntityForName:@"Molecule"
inManagedObjectContext:context];
molecule.name = name;
// Create string scanner
NSScanner *scanner = [NSScanner scannerWithString:(nil == csvString ? @"" : csvString)];
[scanner setCharactersToBeSkipped:[NSCharacterSet whitespaceCharacterSet]];
// Skip any lines that are valid xyz format
BOOL xyzLine = NO;
BOOL firstLineFound = NO;
NSCharacterSet *newlineSet = [NSCharacterSet characterSetWithCharactersInString:@"\n"];
while ( ![scanner isAtEnd] && ( ( xyzLine && firstLineFound ) || !firstLineFound ) ) {
NSString *line = nil;
NSScanner *lineScanner;
double coords[3];
NSString *symbol;
// Attempt to scan in one line
if ( ![scanner scanUpToCharactersFromSet:newlineSet intoString:&line] ) line = @"";
[scanner scanString:@"\n" intoString:NULL]; // Remove any newline at start of line
lineScanner = [NSScanner scannerWithString:line]; // Create scanner for scanning line content
[lineScanner setCharactersToBeSkipped:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
// Now scan symbol and coords, if they exist
xyzLine = [lineScanner scanCharactersFromSet:[NSCharacterSet letterCharacterSet] intoString:&symbol] &&
[lineScanner scanDouble:&(coords[0])] &&
[lineScanner scanDouble:&(coords[1])] &&
[lineScanner scanDouble:&(coords[2])];
// If this line is in valid x, y, z form, add a new atom
if ( xyzLine ) {
firstLineFound = YES;
[molecule addAtomWithPosition:coords andSymbol:symbol];
}
}
return molecule;
}
This is quite a long method, but most of it is related to parsing the XYZ string with instances of NSScanner. The part that is of most interest is the creation of the new Molecule instance:
// Create new molecule
Molecule *molecule = [NSEntityDescription insertNewObjectForEntityForName:@"Molecule"
inManagedObjectContext:context];
molecule.name = name;
The addAtomWithPosition:andSymbol: also creates a new instance, this time belonging to the Atom entity.
-(id)addAtomWithPosition:(double *)position andSymbol:(NSString *)symbol {
Atom *atom = [NSEntityDescription insertNewObjectForEntityForName:@"Atom"
inManagedObjectContext:[self managedObjectContext]];
Element *element = [Element elementWithSymbol:symbol inManagedObjectContext:[self managedObjectContext]];
atom.molecule = self;
atom.element = element;
atom.positionX = [NSNumber numberWithDouble:position[0]];
atom.positionY = [NSNumber numberWithDouble:position[1]];
atom.positionZ = [NSNumber numberWithDouble:position[2]];
return atom;
}
It also uses the elementWithSymbol:inManagedObjectContext: method defined earlier, which first searches for an existing Element, and only creates a new one if no element exists with the symbol passed.
Application Delegate
That covers the model layer of Molecular Core. We just need a few lines in the application delegate to bind things together, and add nice touches like undo/redo and filtering of molecules with a search field.
The Xcode Core Data template project that we began the Molecular Core project with includes a lot of code in the application delegate class. This code sets up a so-called persistent store, a file to store the applications data. The default code creates this file in the Application Support folder, which is fine, but an XML file type is used. We want to use the high performance SQLite database format, so we’ll change that.
Open Molecular_Core_AppDelegate.m, and locate the method persistentStoreCoordinator. Change the end of the method as follows:
url = [NSURL fileURLWithPath: [applicationSupportFolder stringByAppendingPathComponent: @"Library.molcore"]];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error]){
[[NSApplication sharedApplication] presentError:error];
}
return persistentStoreCoordinator;
The NSSQLiteStoreType argument to addPersistentStoreWithType:configuration:URL:options:error: ensures the store will be in SQLite format. The file name has also been changed to ‘Library.molcore’.
Importing Data
When the user presses the add button, a sheet should popup so that a XYZ file can be selected to import the molecular coordinates. Here is an action, and helper, to achieve that.
-(IBAction)import:(id)sender {
NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setAllowsMultipleSelection:NO];
[panel setCanChooseDirectories:NO];
[panel setAllowedFileTypes:[NSArray arrayWithObjects:@"csv", @"txt", nil]];
[panel beginSheetForDirectory:nil file:nil modalForWindow:window modalDelegate:self
didEndSelector:@selector(importPanelDidEnd:returnCode:context:) contextInfo:NULL];
}
-(void)importPanelDidEnd:(id)panel returnCode:(int)returnCode context:(void *)context {
if ( returnCode == NSOKButton ) {
NSString *csvString = [NSString stringWithContentsOfURL:[panel URL]];
[Molecule moleculeFromCSVString:csvString withName:@"New Molecule" inManagedObjectContext:[self managedObjectContext]];
}
}
If the import goes to plan, the Molecule method moleculeFromCSVString:withName:inManagedObjectContext: gets invoked to create the new molecule.
Undo/Redo
One of the advantages making the relationships in your data model explicit is that Core Data can take over a whole lot of difficult to program features for you. One such feature is Undo/Redo — with Core Data it is trivial. Adding the following code to the application delegate is all it takes.
-(BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem {
if ([anItem action] == @selector(undo:)) {
return [[[self managedObjectContext] undoManager] canUndo];
}
else if ([anItem action] == @selector(redo:)) {
return [[[self managedObjectContext] undoManager] canRedo];
}
return YES;
}
-(IBAction)undo:sender {
[[[self managedObjectContext] undoManager] undo];
}
-(IBAction)redo:sender {
[[[self managedObjectContext] undoManager] redo];
}
As you can see, responsibility for undo/redo is off-loaded to an undo manager in the managed object context. Simple.
Finishing the Interface
Earlier on, we left a few gaps in the user interface. Now that the controller code is finished, we can return to it. First, we need to connect up the buttons. The add button should be connected to the import: action of Molecular_Core_AppDelegate. Control and drag from the button to the Molecular_Core_AppDelegate, and choose the import: action.
The remove button should be connected to the remove: action of Molecules Controller. It is also a good idea to connect the Enabled binding of the remove button to the Molecules Controller, with the Controller Key ‘canRemove’. This will cause it to be disabled if there is no molecule selected, for example.
For Undo/Redo, open the MainMenu, and locate the Undo and Redo menu items in the Edit menu. Connect the Undo item to the undo: action of the Molecular_Core_AppDelegate instance. Connect Redo to the redo: method.
The search field is all that remains. Select it, and open the Bindings tab. Locate the Search section, and open the Predicate binding. Bind it to the Molecules Controller with the Controller Key ‘filterPredicate’. Set the Display Name field to ‘Name’, and the predicate field to ‘name contains $value’. This will cause the Molecules Controller to filter its content according to the predicate given, which means only molecules with names that contain the search string will be included.
Build and Go
That’s it for this week. If all has gone well, you should be able to build the app and run it. If all went pair shaped — or you are too lazy to follow along — you can download the completed project.
There is much we have had to gloss over in Core Data. If you want to know more, check out Apple's introduction.
Next time we will look at what is new in Core Data in Leopard. In particular, we will tackle the messy world of migrating from one data model to another.



Comments
Excellent! Thanks for this
Excellent! Thanks for this article, near the top on my todo list.
Could you provide an example csv line? I tried editing a file "O,12,12,12" and opening it in the app but it failed to show any coordinates. I must be doing something wrong. Thanks - Noar
xyz NOT csv
Hi Noar,
Argh! I said csv in the article, but I meant xyz. There is an example file included with the finished app.
Here is an example
O 1. 2.5 1.
H 0. 0. 0.
H 2. 1. 1.
As you can see, it is just space delimited, one atom per line.
Drew
---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org