maxedison
maxedison

Reputation: 17553

Problems implementing the NSBrowserDelegate protocol

I'm able to get my NSBrowser instance to display the correct data in the first column. When I select one of the options, however, the next column simply displays the same set of options. I have read the docs, looked at all of Apple's relevant sample code, and just about everything I could find on the internet but I simply can't figure out the correct way to implement the required methods. The data I'm supplying to the browser is an array of dictionaries. Each dictionary in turn contains a "children" key that is another array of dictionaries. And those dictionaries have their own "children" key that are also arrays of dictionaries, etc. Using JSON for descriptive purposes (objects are dictionaries, arrays are arrays), it looks like this:

data = [
    {
        name: 'David',
        children:[
            {
                name: 'Sarah',
                children: {...}
            },
            {
                name: 'Kevin',
                children: {...}
            }
        ]
    },
    {
        name: 'Mary',
        children:[
            {
                name: 'Greg',
                children: {...}
            },
            {
                name: 'Jane',
                children: {...}
            }
        ]
    }
]

So the first column should show "David" and "Mary". If "David" is selected, the next column should show "Sarah" and "Kevin", and so on.

My current implementation relies on a custom method I created that is supposed to translate the browser's index path into the corresponding NSArray level from the provided data. This method looks like:

- (NSArray *)getSelectionInBrowser:(NSBrowser *)browser
{
    NSArray *selection = browserData;
    NSIndexPath *path = [browser selectionIndexPath];
    for (NSUInteger i = 0; i < path.length; i++) {
        selection = [[selection objectAtIndex:i] objectForKey:@"children"];
    }
    return selection;
}

My implementation of the required NSBrowserDelegate protocol methods looks like:

- (NSInteger)browser:(NSBrowser *)sender numberOfRowsInColumn:(NSInteger)column
{
    return [[self getSelectionInBrowser:sender] count];
}

- (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
    return [[self getSelectionInBrowser:browser] count];
}

- (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
    return [self getSelectionInBrowser:browser];
}

- (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
    return ![item isKindOfClass:[NSMutableArray class]];
}

- (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
    return nil;
}

- (void)browser:(NSBrowser *)browser willDisplayCell:(NSBrowserCell *)cell atRow:(NSInteger)row column:(NSInteger)column {
    NSArray *selection = [self getSelectionInBrowser:browser];
    cell.title = [[selection objectAtIndex:row] objectForKey:@"name"];
}

The first column of the NSBrowser is populated with the correct names. However, as soon as I make a selection the program crashes with the error -[__NSArrayM objectAtIndex:]: index 4 beyond bounds [0 .. 0]. After doing some debugging, the line of code it crashes on is the objectAtIndex: call in my custom getSelectionInBrowser:.

That doesn't fully surprise me because even before the crash I figured I was doing something wrong by relying on that custom method to retrieve the current selection. I imagine this work should be done within the delegate methods themselves and, when implemented correctly, the current selection should be accessible in the item variable that is provided in many of those methods. However, I couldn't get that to work. The item variable always seemed to be simply the root data object rather than reflecting the most "drilled-down" selection.

So how do I correct my implementation?

Upvotes: 3

Views: 1190

Answers (1)

maxedison
maxedison

Reputation: 17553

Solved it! Here is my final working code. No need for that custom getSelection... method, and a couple of the delegate methods I had were unnecessary (only used of you are NOT going with the "item-based API").

- (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
    if (item) {
        return [[item objectForKey:@"children"] count];
    }
    return [browserData count];
}

- (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
    if (item) {
        return [[item objectForKey:@"children"] objectAtIndex:index];
    }
    return [browserData objectAtIndex:index];
}

- (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
    return [item objectForKey:@"children"] == nil;
}

- (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
    return [item objectForKey:@"name"];
}

The first method is how you tell the NSBrowser the number of rows there should be. The second method is where you determine what data should be represented in a given row (index). In both cases, you must first check to see if item actually exists. If it doesn't, that's because you are at the root of the data (first column in the NSBrowser). Only when a row (or item!) in the NSBrowser gets selected will the item variable hold anything. The final method should return the string you wish to show in the given row.

Hopefully this helps people in the future.

Upvotes: 4

Related Questions