< Previous PageNext Page > Hide TOC

Writing an Outline View Data Source

Outline views support a data-source delegate in addition to a standard delegate object. The delegate object handles all typical delegate responsibilities for the outline view, such as modification of selection management behavior. The data-source provides data and information about that data to the outline view, and is responsible for managing the data. Although an outline view does not require a delegate, it must have a data source to display information.

Contents:

Data Source Requirements
The Data Source and Memory Management
Sample Data Source Implementation


Data Source Requirements

Like an instance of NSTableView, an instance of NSOutlineView gets all of its data from an object that you provide, called its data source. Your data source object can store records in any way you choose, but it must be able to identify them by their position in the hierarchy through the NSOutlineViewDataSource informal protocol. The data source must minimally implement the data access methods (outlineView:child:ofItem:, outlineView:isItemExpandable:, outlineView:numberOfChildrenOfItem:, and outlineView:objectValueForTableColumn:byItem:). To specify the root item in any of these methods, nil is sent as the method’s item argument. If you want to allow the user to edit items, you must also implement a method for changing the value of an attribute (outlineView:setObject:forTableColumn:byItem:).

Typically the data source itself manages a collection of model objects each of which knows what their value is, whether they represent a leaf node, and how many (if any) child objects they have.

The Data Source and Memory Management

Just like a table view, an outline view uses the data source solely to get information. An outline view does not own its data source (see Communicating With Objects). Similarly, it does not own the objects it gets from the data source—if they are released your application is likely to crash unless you tell the outline view to reload its data.

The data source is a controller object, and you are responsible for ensuring that it is not deallocated before the outline view is finished with it (typically the data source is an object such as the document object in a document-based application, so there is no additional work to do). The data source is in turn responsible for retaining all of the objects it provides to an outline view, and updating the outline view when there’s a change to the model. It is therefore not safe to release the root item—or any children—until you’re no longer displaying it in the outline view. If you need to dispose of the root item, then you should ensure that references to it are nullified, and that the outline view is updated to ensure that no attempt is made to display other items that may also have been disposed of, as in the following example.

    [rootItem release];
    rootItem = nil;
    [outlineView reloadData];

Sample Data Source Implementation

The following example shows the implementation of a data source class used in conjunction with an outline view to display contents of the file system, and of a class used to represent entries in the file system. Listing 1 shows the implementation of the data source class. Listing 2 shows the implementation of a class used to represent entries in the file system. The singleton rootItem instance is used as the root object in the example in Listing 1.

Listing 1  Implementation of Outline View Data Source

@implementation DataSource
// Data Source methods
 
- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
    return (item == nil) ? 1 : [item numberOfChildren];
}
 
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
    return (item == nil) ? YES : ([item numberOfChildren] != -1);
}
 
- (id)outlineView:(NSOutlineView *)outlineView
    child:(int)index
    ofItem:(id)item
{
    return (item == nil) ? [FileSystemItem rootItem] : [item childAtIndex:index];
}
 
- (id)outlineView:(NSOutlineView *)outlineView
    objectValueForTableColumn:(NSTableColumn *)tableColumn
    byItem:(id)item
{
    return (item == nil) ? @"/" : [item relativePath];}
 
@end

Listing 2  Implementation of Outline View Data Source Item

@interface FileSystemItem : NSObject
{
    NSString *relativePath;
    FileSystemItem *parent;
    NSMutableArray *children;
}
 
+ (FileSystemItem *)rootItem;
- (int)numberOfChildren;// Returns -1 for leaf nodes
- (FileSystemItem *)childAtIndex:(int)n;// Invalid to call on leaf nodes
- (NSString *)fullPath;
- (NSString *)relativePath;
 
@end
 
 
@implementation FileSystemItem
 
static FileSystemItem *rootItem = nil;
#define IsALeafNode ((id)-1)
 
- (id)initWithPath:(NSString *)path parent:(FileSystemItem *)obj
{
    if (self = [super init])
    {
        relativePath = [[path lastPathComponent] copy];
        parent = obj;
    }
    return self;
}
 
 
+ (FileSystemItem *)rootItem
{
    if (rootItem == nil) rootItem = [[FileSystemItem alloc] initWithPath:@"/" parent:nil];
    return rootItem;
}
 
 
// Creates, caches, and returns the array of children
// Loads children incrementally
- (NSArray *)children
{
    if (children == NULL)
    {
        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSString *fullPath = [self fullPath];
        BOOL isDir, valid = [fileManager fileExistsAtPath:fullPath isDirectory:&isDir];
 
        if (valid && isDir)
        {
            NSArray *array = [fileManager directoryContentsAtPath:fullPath];
            int cnt, numChildren = [array count];
            children = [[NSMutableArray alloc] initWithCapacity:numChildren];
 
            for (cnt = 0; cnt < numChildren; cnt++)
            {
                FileSystemItem *newChild = [[FileSystemItem alloc] initWithPath:[array objectAtIndex:cnt] parent:self];
                [children addObject:newChild];
                [newChild release];
            }
        }
        else
        {
             children = IsALeafNode;
        }
    }
    return children;
}
 
 
- (NSString *)relativePath
{
    return relativePath;
}
 
 
- (NSString *)fullPath
{
    // If no parent, return our own relative path
    if (parent == nil) return relativePath;
 
    // recurse up the hierarchy, prepending each parent’s path
    return [[parent fullPath] stringByAppendingPathComponent:relativePath];}
 
- (FileSystemItem *)childAtIndex:(int)n
{
    return [[self children] objectAtIndex:n];
}
 
- (int)numberOfChildren
{
    id tmp = [self children];
    return (tmp == IsALeafNode) ? (-1) : [tmp count];
}
 
 
- (void)dealloc
{
    if (children != IsALeafNode) [children release];
    [relativePath release];
    [super dealloc];
}
 
@end



< Previous PageNext Page > Hide TOC


© 2001, 2006 Apple Computer, Inc. All Rights Reserved. (Last updated: 2006-06-28)


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.