Question

Please explain why I am having problems with the line below that I have commented out.

It is causing a EXC_BAD_ACCESS in outlineView:objectValueForTableColumn:byItem:.

A gist with the full class is at: https://gist.github.com/onato/9d12bbbf5c4135673f24

- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
    if (!item) {
        item = self.data;
    }
    id returnValue = @"";
    if ([item isKindOfClass:[NSArray class]]) {
        returnValue = @"value";//[item objectAtIndex:index];
    }

//    return @{@"index":@(index), @"value":returnValue}; // produces EXC_BAD_ACCESS in outlineView:objectValueForTableColumn:byItem:
    return returnValue;
}

I have tried creating a really basic project with just this datasource and nothing else and I still see the problem.

Was it helpful?

Solution

You can't make up items on the fly in outlineView:child:ofItem:. All of your items must already exist, or at least continue to exist afterward until they are deleted (i.e., removed on the user's behalf from both view and model) or until whatever the outline view is showing is dismissed (e.g., document closed).

Dictionary literals (@{ … }) represent the creation of a dictionary at that point. Whenever your program gets to that line, it will create a new dictionary, every time, even for the same child of the same item. (This is necessarily true when the dictionary includes something that isn't constant, such as the values of both index and returnValue.)

Even if you keep the dictionaries around, though, using plain old dictionaries and/or arrays for your model makes for very hairy code very quickly.

The solution

Make a simple subclass of NSObject with two properties:

  • value (or something more specific), which is whatever kind of value you're showing in the outline view
  • children (or something more specific), which is an array of whatever descendant items each item may contain

Then keep an array of those objects. When asked for a child of nil, return one of the objects in that array. When asked for a child of an item, that item will be one of these objects, so return one of its children.

When asked for the object value (for what I assume is your one and only column) of an item, return the item's value. If you have multiple columns, have a property for each column.

Most importantly, create all of these objects before the outline view is even visible and keep them around until you are done with them (whether by deletion of an item or by dismissal of the view). Don't create items as needed and then expect the outline view to hang on to them for you—that's not its job; that's your job as the controller.

The items don't have to all be the same class; if it makes sense for your app, you can have Foos owning Bars and Bars owning Bazzes. If your needs are simple, it may make more sense for everything to be a Foo. Do whichever makes sense. Either way, custom objects add clarity to your code.

More to the point, it's also much more obvious when you're dropping a custom object on the floor versus dropping a dictionary created by a literal. It's much easier to see return [[Foo alloc] init…] and be reminded “oh, right, I need to hang on to that”.

OTHER TIPS

In general it is poor practice to have a value that can be returned without initializing.

id returnValue; should be id returnValue = nil; in specific if there is an object in self.data that is not in fact an NSArray (or child of it), then you will return an undefined pointer to an object, which will almost certainly result in something unfortunate.

What assurance do you have that index is in fact within the bounds of item? This seems like it would generate a bad access exception?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top