First up on my list of tasks to perform is to set up the sidebar to display the groups and sections. When choosing from the elements in Interface Builder (IB), there are two options: Outline Views, or Source Lists. An Outline View is a stock standard expandable/collapsible hierarchical listing. Source lists, on the other hand, or more like the sidebar in Mac Mail, with icons and headers for sections.
Naturally, the source list is what I’ve chosen. Technically, however, the source list is really an NSOutlineView, but is view-based rather than cell-based. This means that the cells are NSView objects, and can contain any elements you want. It also makes dealing with them a touch more difficult. From now on I will refer to the NSOutlineView and Source List to mean the same thing.
To get the data into the Source List, the custom controller class (TodoController) needs to be set as its delegate and data source. When the source list initialises or redraws, it will call methods on the data source to request certain information. These methods are: outlineView:isItemExpandable:, outlineView:numberOfChildrenOfItem:, outlineView:child:ofItem:, and outlineView:objectValueForTableColumn:byItem:.
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(TodoGroup *)item { return ([[item childGroups] count] > 0); } - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(TodoGroup *)item { if (item == nil) { return [rootGroups count]; } else { return [[item childGroups] count]; } } - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { if (item == nil) { return [rootGroups objectAtIndex:index]; } else { return [[item childGroups] objectAtIndex:index]; } } - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { return [item groupName]; }
The first two are used for information only, to help the outline view build its structure, with the first informing the view about whether it can be expanded (i.e. has child groups), and the second reporting how many children it has.
The third method is used to get a specific child group of an a section or parent group. The “child” variable is actually an integer that will directly correspond to the an index of the array where a child exists.
The last method was where everything was coming unstuck. It should have worked, but the code simply wasn’t being called. There was nothing in the data source protocol documentation that would point me to why it didn’t work, so it had me stumped. I tried copying code directly from the example code provided by Apple, but it still didn’t work (even though the example project ran fine).
Anyway, it turns out (after a frustrating hour or so of Google-fu and trial and error), that the necessary method to implement is not, in fact, a part of the data source, it is a part of the delegate. The reason for this is that NSOutlineView is based on NSTableView, which uses a delegate method to return the view for the cell.
So, for view-based outline views/source lists, we need to implement outlineView:viewForTableColumn:item:
- (NSView *)outlineView:(NSOutlineView *)ov viewForTableColumn:(NSTableColumn *)tableColumn item:(TodoGroup *)item { NSTableCellView *result; if ([item isHeaderGroup]) { result = [ov makeViewWithIdentifier:@"HeaderCell" owner:self]; } else { result = [ov makeViewWithIdentifier:@"DataCell" owner:self]; } [[result textField] setStringValue:[item groupName]]; return result; }
And that’s it. When I plug in some sample data, it builds the source list correctly, finally! Now, this method will need to be modified down the track when I add a token to indicate the number of incomplete tasks, but for now it’s functionally usable.
Next stop is buttons to add and remove groups and sections from the list.