objective-ccocoansoutlineview

NSOutlineView - object value is being returned, but only the placeholder title "Table View Cell" is displayed


I am writing a small application tentatively called "CocoaMix", the purpose of which is to test out code that I have written for accessing the accessibility hierarchy on various UIs. Essentially what I am trying to accomplish is an NSOutlineView on the left and an "example" view on the right of a test user interface.

So far, for the data source implementation I have:

@interface CMTopLevelCategory : NSObject

@property (readonly) NSString *name;

@property (readonly) NSArray *examples;

- (id)initWithName:(NSString *)name examples:(NSArray *)examples;

@end


@interface CMExample : NSObject

@property (readonly) NSString *name;

- (id)initWithName:(NSString *)name;

@end


@interface CMSideOutlineViewDataSource : NSObject <NSOutlineViewDataSource>

@end


//...


#define CMNameColumnIdentifier @"name"
#define CMCountColumnIdentifier @"count"

@interface CMSideOutlineViewDataSource ()

@property NSArray *topLevelCategories;

@end

@implementation CMSideOutlineViewDataSource

- (id)init {
    self = [super init];
    if (self) {
        CMExample *largeTableExample = [[CMExample alloc] initWithName:@"Large Table"];
        CMTopLevelCategory *tablesCategory = [[CMTopLevelCategory alloc] initWithName:@"Tables" examples:@[ largeTableExample ]];

        _topLevelCategories = @[ tablesCategory ];
    }
    return self;
}

- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
    if (item) {
        if ([item isKindOfClass:CMTopLevelCategory.class]) {
            CMTopLevelCategory *topLevelCategory = item;
            return [topLevelCategory.examples objectAtIndex:index];
        }
    } else {
        return [self.topLevelCategories objectAtIndex:index];
    }
    return nil;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
    return [item isKindOfClass:CMTopLevelCategory.class];
}

- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
    if (item) {
        if ([item isKindOfClass:CMTopLevelCategory.class]) {
            CMTopLevelCategory *topLevelCategory = item;
            return [topLevelCategory.examples count];
        }
    } else {
        return [self.topLevelCategories count];
    }
    return 0;
}

- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
    if ([item isKindOfClass:CMTopLevelCategory.class]) {
        CMTopLevelCategory *topLevelCategory = item;
        if ([tableColumn.identifier isEqualToString:CMNameColumnIdentifier]) {
            return topLevelCategory.name;
        } else if ([tableColumn.identifier isEqualToString:CMCountColumnIdentifier]) {
            return [NSNumber numberWithUnsignedInteger:[topLevelCategory.examples count]];
        }
    } else if ([item isKindOfClass:CMExample.class]) {
        CMExample *example = item;
        if ([tableColumn.identifier isEqualToString:CMNameColumnIdentifier]) {
            return example.name;
        }
    }
    return @"default value";
}

@end

To explain this, the top level items are supposed to be categories of examples like "Tables", and then for each category there are going to be some example UIs (e.g. "Large Table" in the "Tables" category of examples).

When I build and run, breakpoints that I have set in outlineView:objectValueForTableColumn:byItem: are triggered, but the text in the NSOutlineView is still the placeholder "Table Cell View" text:

Screenshot of the "CocoaMix" application window

What am I doing wrong?


Solution

  • I'm going to take some guesses. You're probably using a view-based outline view. The cell views are probably NSTableCellViews with an NSTextField in each.

    The outline view takes the object value that your data source returns from its -outlineView:objectValueForTableColumn:byItem: method and it calls -setObjectValue: on the cell view, if it responds to that method. NSTableCellView does.

    So, now your table cell view has had its objectValue set. What does that do? Possibly nothing.

    You can use bindings to bind the subviews to the table cell view, with key paths which go through the objectValue. So, you could bind the Value binding of your text field to the table cell view with a key path of objectValue. That would result in the text field showing the value.

    Another approach is to subclass NSTableCellView and use that custom subclass as the cell view in your outline view. Then, override -setObjectValue: and, in addition to calling through to super, you could pass the object value on to the subviews. To access the subviews, you could connect them to the standard textField and imageView outlets of NSTableCellView or you could add more outlets to your custom subclass, connect them in IB, and use those.

    The bindings approach is easiest, I think. Also, it provides more flexibility. Your implementation of -outlineView:objectValueForTableColumn:byItem: could just return the item. Then, the binding of your text field could use objectValue.name as the model key path. If you add more subviews, they could use different key paths to show different aspects of the item.

    For the different columns, you would use different cell view sub-hierarchies and the bindings of the subviews of those would use different key paths but the same objectValue. The idea is that the row represents a given item, so the objectValue would be that item, and the columns would just use different properties from the item.

    Another possible approach, if you're sure your outline view is going to only have text fields in the cell views, is to not use NSTableCellView (or a subclass), at all. Instead, use bare NSTextFields for your cell views. In that case, when the outline view calls -setObjectValue: on the cell view, that will be the text field (which responds to that method), and will set its value with no extra steps.