17

I'm trying to implement the new view-based OutlineView as a source list in my Mac app. I can't get values to display, though, so I made a small test app from the Core Data app template, and can't get it working right in there, either.

I defined two simple classes in my data model; let's call them "Parent" and "Child". Parent has a single Attribute, "name", and a single relationship, "children". name is an optional string, and children is an optional to-many relationship to Child. Child has the same "name" attribute and a to-one "parent" relationship that is the inverse of children. I generated custom classes for both of those, and wrote a stub in Child for children that returns nil.

I dragged a Source List from the Object library onto my XIB, and dropped in a Tree Controller. The Tree Controller's Children Key Path is set to "children", it's in Entity Name mode, with "Parent" as the Entity Name, Prepares Content checked, and its Managed Object Context set to the app delegate's context. The Tree Controller is the data source of the outline view, and I bound the data cell's text view to Table Cell View, with the "objectValue.name" key path.

in -applicationDidFinishLaunching: I create two Parent instances, one with a Child, and assign the name property of every object.

The actual problem

No text

Now, with that setup out of the way, I get rows showing up in the source list, but the text fields are empty, even though they're bound. I don't think I should need to do anything else, since I'm using bindings, and I'm fairly certain binding to the objectValue property is the right thing. What's going wrong?

I can provide more detail if necessary, but I'm pretty sure that covers everything I did.

Dov
  • 15,530
  • 13
  • 76
  • 177
  • Did you get this to work? I had the same problem, so I tried to duplicate your project. But I'm using a doc based app. My delegate methods is never called and I get no text values in my cells. They do, however contain the correct data. D you have working code anywhere I can have a look at? Would greatly appreciate it! I can upload my test project to github. – Mikael Dec 23 '12 at 08:41
  • 1
    Did you mark your view controller as the delegate? If the delegate methods aren't getting called at all, then your wiring is probably incorrect. If that doesn't fix it for you, you should post your own question. – Dov Dec 23 '12 at 15:07
  • Can't believe I missed that. Thanks a lot! – Mikael Dec 23 '12 at 18:57

4 Answers4

25

Wow, it's like me from two weeks ago is asking this question.

Anyway, if you're anything like me, the problem is that,
for view-based NSOutlineViews, you need to implement the

- (NSView *)outlineView:(NSOutlineView *)outlineView
     viewForTableColumn:(NSTableColumn *)tableColumn
                   item:(id)item;

delegate method and return the NSTableCellView you set up,
or they'll just give you a blank line. The easiest way to do this is to just call

[outlineView makeViewWithIdentifier:@"MyCell" owner:self]

replacing MyCell with whatever you typed in as the "User Interface Item Identifier"
in the Identity Inspector for your NSTableCellView.

Objective-C:

- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
   return [outlineView makeViewWithIdentifier:@"MyCell" owner:self];
}

Swift:

func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
    return outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("MyCell"), owner: self)
}

UPDATE 2018-08-02:

Actually, you don't need to set the delegate. Here is how I got it working (tested with NSTreeController, but should work with NSArrayController as well):

  • Bind each column object to arrangedObjects (without Model Key Path)
  • Bind the inner-most custom view (e.g., label field) to objectValue.yourCustomValue
  • Shouldn't be necessary but if this doesn't work try setting the identifier for the column and for the TableCellView. Make sure both identifiers are identical. Repeat that for the remaining columns with different identifiers.

Screenshot: Bindings for View Based NSOutlineView

Community
  • 1
  • 1
Boaz Stuller
  • 2,534
  • 21
  • 16
  • 6
    You need to implement this in addition to using bindings. Bindings can still handle setting the cell view's objectValue, and for binding the cell view's subviews to that objectValue. But this method is still required to tell the outline view which cell view to use for a particular row/column. – Boaz Stuller Aug 17 '11 at 16:35
  • 6
    This is necessary, since the Source List uses two different cells in the same column, HeaderCell and DataCell by default, and it can't pick one for you, like it could if you only had one cell and had its identifier set to Automatic. – Dov Aug 17 '11 at 21:01
  • Thanks for this answer! I almost gave up searching. But I have the following problem: When I set an identifier the method `-outlineView:viewForTableColumn:item:` never gets called. When I don't set an identifier `- outlineView:objectValueForTableColumn:byItem:` is invoked. What's the problem here? I also set the Deployment Target to 10.7 but there's no change: `-outlineView:viewForTableColumn:item:` does not get called. – Paul Aug 20 '11 at 16:36
  • Oh, pretty easy to solve: Of course you need to set the delegate of your Outline View to your custom DataSource. – Paul Aug 20 '11 at 17:59
  • @Dov Good response, can you direct me on where this info was found? I'm not finding any thing in Apple's docs – Just a coder Jun 19 '13 at 17:41
  • @Jai, which info? Between documentation on NSTableView (of which NSOutlineView is a subclass), sample code in the documentation, and WWDC talks from the year of this question/answer: 2011, I'm sure it exists. If you find any pertinent links, go ahead and add another comment. A quick search turned up this Apple-provided guide: https://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/TableView/PopulatingView-TablesProgrammatically/PopulatingView-TablesProgrammatically.html – Dov Jun 21 '13 at 14:25
6

As Boaz noted above, you need to implement the Delegate method to return a view.

Quite a mystery considering I could not find that method in the Docs.

Regarding the type of the (id)item parameter, it's a NSTreeControllerTreeNode which is an undocumented subclass of NSTreeNode. If you cast it you can get the cell's object, and return different view based what kind of object is, or whatever attributes of that object determine the cell view type:

- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
    NSTableCellView *view = nil;

    NSTreeNode *node = item;

    if ([node.representedObject isKindOfClass:[Group class]]) {
        view = [outlineView makeViewWithIdentifier:@"HeaderCell" owner:self];
    } else {
        view = [outlineView makeViewWithIdentifier:@"DataCell" owner:self];
    }

    return view;
}
pkamb
  • 33,281
  • 23
  • 160
  • 191
Ryan
  • 5,416
  • 1
  • 39
  • 36
4

This appeared to be a change for Xcode 4 or thereabouts. Interface builder adds two NSTableCellView objects under the NSOutlineView. If you delete the NSTableCellView objects you return to a saner (or at least documented) world where you need would implement the methods:
outlineView:dataCellForTableColumn:item outlineView:willDisplayCell:forTableColumn:item

...or at least you do if you want a source list look. In any case this is how the SourceView sample is setup and is why, when you try to recreate the SourceView sample that you can get in such a mess.

Alternatively if you want to continue to use the NSTableCellView objects (which are quite useful) then you can:

  • bind the NSOutlineView 'Content' to your TreeController.arrangedObjects

  • bind the NSTextField (and/or NSImageView) under NSTableCellView to 'Table Cell View' with a model key path of objectValue.< key >

pkamb
  • 33,281
  • 23
  • 160
  • 191
kuwerty
  • 380
  • 4
  • 4
1

I have created a little sample project which popuplates also popuplates an NSOutlineView, not with CoreData but the crucial factor is, like @boaz-stuller stated that the correct cell is selected (similar to how you handle UITableViewCells in iOS.

So in my case I have implemented the method like so:

- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {

    if ([self isHeader:item]) {
        return [outlineView makeViewWithIdentifier:@"HeaderCell" owner:self];
    } else {
        return [outlineView makeViewWithIdentifier:@"DataCell" owner:self];
    }
}

Check out besi/mac-quickies on github. Most of the stuff is either done in IB or can be found in the AppDelegate

screenshot

Besi
  • 22,579
  • 24
  • 131
  • 223