Simply Hacking

Mac OS X and iOS Programming

Source List With Core Data

| Comments

Last week I decided to look into how to create a source list that is backed by Core Data with section headers created from code. New to Cocoa I set out to Google for a solution but surprisingly enough I didn’t find a lot about how to achieve this (or I simply googled for the wrong things). After a few days I managed to derive, what I believe, an elegant solution to this and figured it could be useful for others wanting to do this.

The planned source list

Approaches

My first plan was to create some kind of layer in between Core Data and the NSTreeController but that quickly added a lot of complexity and code. The second solution was to create the headers and put them in Core Data the first time you start the application. While this was easily done it felt a bit hackish.

After spending a couple of days with this, trying out various solutions and ideas me and Richard discussed the topic, wondering if maybe Core Data had some builtin support for this.

Twenty minutes and some fifty lines of code later I looked at the screen, amazed at how awesome Core Data is. This is what I ended up with, mind though that it’s not a full tutorial but only the crucial steps, you still need to know how to connect it all with the NSTreeController and NSOutlineView.

The Solution

The idea is to use two different persistent stores, the regular XML store and an in memory store for the code generated entites.

For the purpose of this example the data model is very simple, two entities called Section (for the code generated section headers) and Item (for the entities that are read from Core Data).

Section will have a name attribute and a one-to-many relationship to Item called children. Item will have a name and a reverse to-one relationship back to Section called section. Make sure you make this relationship transient to tell Core Data that this relationship only exists runtime and should not be stored.

The data model

The NSTreeController has a property called childrenKeyPath that defines the key path to retrieve an array of children from each of the nodes in the tree. All nodes displayed in the tree need to respond to this key path. Since Items don’t have children it is not included in the data model (though you could have added it in the model designer but in my opinion isn’t as nice), I will instead create a subclass of NSManagedObject to represent an Item.

Select the Item entity in the model designer and create a new file (File->New File and choose Managed Object Class from the Cocoa section). Simply name it Item.m (and check the Also create “Item.h”) box. Add a method children to your Item class.

1
2
3
4
5
6
7
8
/* In Item.h */
- (id)children;

/* In Item.m */
- (id)children
{
    return nil;
}

This method will be called by the NSTreeController when it is populating the tree and since an Item doesn’t have any children it should return nil.

That’s it for the model, time to setup the stores.

I extended the method persistentStoreCoordinator in the auto generated application delegate to create a second store with the URL memory://store. This store should be of the type NSInMemoryStoreType which means that any entity tied to it won’t be saved to disk.

1
2
3
4
5
6
7
8
url = [NSURL URLWithString:@"memory://store"];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType
                                              configuration:nil
                                                        URL:url
                                                    options:nil
                                                      error:&error]) {
    [[NSApplication sharedApplication] presentError:error];
}

Both of these stores act in the same context so the layers on top of Core Data won’t know the difference between entites in one store or another.

With the store in place the Section entities are created when the application is started, I added the code to the application delegate init method. After I create the Section I populate it with the result of fetching all my Item entities from Core Data (lines 30-33 below).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- (NSArray *)fetchAllWithEntity:(NSString *)entity
                          error:(NSError **)error
{
    NSFetchRequest *request;
    NSEntityDescription *desc;

    desc = [NSEntityDescription entityForName:entity
                       inManagedObjectContext:[self managedObjectContext]];

    request = [[[NSFetchRequest alloc] init] autorelease];
    [request setEntity:desc];

    return [[self managedObjectContext] executeFetchRequest:request error:error]; 
}

- (id)init
{
    [super init];

    NSError *error;
    NSURL *url = [NSURL URLWithString:@"memory://store"];
    id memoryStore = [[self persistentStoreCoordinator] persistentStoreForURL:url];

    section = [[NSEntityDescription insertNewObjectForEntityForName:@"Section"
                                             inManagedObjectContext:[self managedObjectContext]] retain];
    [section setValue:@"My section" forKey:@"name"];
    [[self managedObjectContext] assignObject:section
                            toPersistentStore:memoryStore];

    NSArray *items = [self fetchAllWithEntity:@"Item" error:&error];
    for (id item in items) {
        [item setValue:section forKey:@"section"];
    }

    return self;
}

Here the message assignObject:toPersistentStore: is sent to the NSManagedObjectContext to put the newly created Section in the in memory store so that it is not saved to disk with the Items.

I also added an action to my application delegate to create new Items and make them children to the Section.

1
2
3
4
5
6
- (void)addItem:(id)sender
{
    id newObject = [NSEntityDescription insertNewObjectForEntityForName:@"Item"
                                                 inManagedObjectContext:[self managedObjectContext]];
    [newObject setValue:section forKey:@"section"];
}

That’s it! Now a regular NSTreeController can be used in entity mode by setting the entity to Section as you would normally do.

I hope you found it useful, if you did, make sure you share it with your friends and subscribe to my RSS feed or follow me on twitter for notifications about future posts.

I’d love to get feedback on better solutions, improvements or just comments on what you think.

Expanding NSOutlineView Nodes at Application Start

| Comments

A problem when you want to expand some items in an NSOutlineView programatically at application start is that the NSTreeController prepares it’s content after awakeFromNib is called on your controller.

To ensure that the data is loaded before you try to expand the items you can observe the content key on the tree controller and expand the nodes as you receive the the observeValueForKeyPath: message as shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if (object == treeController) {
        // Expand the first row (which is our section header)
        [sourceList expandItem:[sourceList itemAtRow:0]
                expandChildren:NO];
        [treeController removeObserver:self
                            forKeyPath:@"content"];
    }
}

- (void)awakeFromNib
{
    // Listen on the treeController to expand the root node 
    // when it has prepared it's content.
    [treeController addObserver:self
                     forKeyPath:@"content"
                        options:0
                        context:nil];
}

Retrieving the Real Object When Using NSOutlineView With an NSTreeController

| Comments

As I just started out with Cocoa and started looking into the NSTreeController and NSOutlineView I realized that the objects seen by the outline view isn’t the same as the ones you see when working with the tree controller.

The NSTreeController wraps your objects into another object and it took me some digging before I realized that you need to call representedObject to fetch the real object.

Example from implementing an NSOutlineView delegate:

1
2
3
4
5
6
7
8
9
10
/* NSOutlineView Delegate Method */
- (BOOL)outlineView:(NSOutlineView *)view
        isGroupItem:(id)item
{
    if ([item representedObject] == section) {
        return YES;
    }

    return NO;
}

Removing a Remote Branch in Git

| Comments

This is a repost from my old blog

This is something I’ve had to checkup a few times so I figured it would be useful both for myself and for others to keep around in a blog post.

To remove a remote branch you created in Git just push to it like:

1
$ git push origin :name-of-branch

Using Twitter4R on Mac OS X

| Comments

This is a repost from my old blog

Was playing around a bit with the Twitter4R library the other day and realized that in order to make it to work on Mac OS X (Leopard) you need to also require ‘time’. Or you will get an error similar to

1
lib/twitter/model.rb:268:in `init’: undefined method `parse’ for Time:Class (NoMethodError).

A small snippet to display my public tweets:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
require 'rubygems'
gem('twitter4r', '0.3.0')
require 'twitter'

# Required on Mac OS X Leopard
require 'time'

twitter = Twitter::Client.new

m5h_timeline = twitter.timeline_for(:user, :id => 'm5h')

m5h_timeline.each do |status|
  puts status.text
end