17

Has anybody found a clear, concise example or guide on how to implement a source list using the view-based NSOutlineView introduced in Lion? I've looked at Apple's example project, but without any sense of direction or explanation, I'm finding it difficult to grasp the concept of exactly how they work.

I know how to use the excellent PXSourceList as a fallback, but would really like to start using view-based source lists instead if at all possible.

Brian
  • 14,610
  • 7
  • 35
  • 43
John Wells
  • 1,139
  • 1
  • 10
  • 27

2 Answers2

30

You tagged this with the cocoa-bindings tag, so I assume you mean "with bindings." I whipped up a quick example. Start from a new non-document-based Cocoa Application template in Xcode. Call it whatever you like. First I added some code to make some fake data to bind to. Here's what my AppDelegate header looks like:

#import <Cocoa/Cocoa.h>

@interface SOAppDelegate : NSObject <NSApplicationDelegate>

@property (assign) IBOutlet NSWindow *window;

@property (retain) id dataModel;

@end

And here's what my AppDelegate implementation looks like:

#import "SOAppDelegate.h"

@implementation SOAppDelegate

@synthesize window = _window;
@synthesize dataModel = _dataModel;

- (void)dealloc
{
    [_dataModel release];
    [super dealloc];
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    // Insert code here to initialize your application

    // Make some fake data for our source list.
    NSMutableDictionary* item1 = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"Item 1", @"itemName", [NSMutableArray array], @"children", nil];
    NSMutableDictionary* item2 = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"Item 2", @"itemName", [NSMutableArray array], @"children", nil];
    NSMutableDictionary* item2_1 = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"Item 2.1", @"itemName", [NSMutableArray array], @"children", nil];
    NSMutableDictionary* item2_2 = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"Item 2.2", @"itemName", [NSMutableArray array], @"children", nil];
    NSMutableDictionary* item2_2_1 = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"Item 2.2.1", @"itemName", [NSMutableArray array], @"children", nil];
    NSMutableDictionary* item2_2_2 = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"Item 2.2.2", @"itemName", [NSMutableArray array], @"children", nil];
    NSMutableDictionary* item3 = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"Item 3", @"itemName", [NSMutableArray array], @"children", nil];

    [[item2_2 objectForKey: @"children"] addObject: item2_2_1];
    [[item2_2 objectForKey: @"children"] addObject: item2_2_2];

    [[item2 objectForKey: @"children"] addObject: item2_1];
    [[item2 objectForKey: @"children"] addObject: item2_2];

    NSMutableArray* dataModel = [NSMutableArray array];

    [dataModel addObject: item1];
    [dataModel addObject: item2];
    [dataModel addObject: item3];

    self.dataModel = dataModel;
}

@end

There's no particular significance to the fake data structure I created, I just wanted to show something with a couple of sub-levels, etc. The only thing that matters is that the key paths you specify in the bindings in Interface Builder line up with the keys in your data (fake data in this case.)

Then select the MainMenu.xib file. In the IB editor, do the following steps:

  1. Use the Object Library (Ctrl-Cmd-Opt-3) to add an NSTreeController to your .xib.
  2. Select the NSTreeController, and using the Attributes Inspector (Cmd-Opt-4) set Key Paths > Children to children (for this example; For your data, this should be whatever returns the array of child objects.)
  3. With the NSTreeController still selected, use the Bindings Inspector (Cmd-Opt-7) to bind the Content Array to the AppDelegate, with a Model Key Path of dataModel
  4. Next use the Object Library (Ctrl-Cmd-Opt-3) to add an NSOutlineView to your .xib.
  5. Arrange it to your satisfaction inside the window (typically the entire height of the window, flush against the left-hand side)
  6. Select the NSOutlineView (note that the first time you click on it, you have likely selected the NSScrollView that contains it. Click on it a second time and you'll have drilled-down to the NSOutlineView itself. Note that this is MUCH easier if you widen the area on the left of the IB editor where all the objects are -- this allows you see the objects as a tree, and navigate and select them that way.)
  7. Using the Attributes Inspector (Cmd-Opt-4) set the NSOutlineView:
    • Content Mode: View Based
    • Columns: 1
    • Highlight: Source List
  8. Using the Bindings Inspector (Cmd-Opt-7) bind "Content" to "Tree Controller", Controller Key: arrangedObjects (This is where the behavior of View-based NSTableView/NSOutlineViews starts to diverge from NSCell-based ones)
  9. In the Object List (mentioned in #6), expand the view hierarchy of the NSOutlineView and select Static Text - Table View Cell.
  10. Using the Bindings Inspector (Cmd-Opt-7) bind Value to Table Cell View, Model Key Path: objectValue.itemName (I've used itemName in the fake data, you would want to use whichever key corresponded to the name of your data items)

Save. Run. You should see a source list, and once you've expanded the nodes with children, you might see something like this:

enter image description here

If you're in the Apple Developer Program, you should be able to access the WWDC 2011 Videos. There's one specifically dedicated to working with View-based NSTableView (and NSOutlineView) and it includes pretty thorough coverage of bindings.

Hope that helps!

ipmcc
  • 29,581
  • 5
  • 84
  • 147
  • 1
    FYI the WWDC 2011 video is session 120 titled View Based NSTableView. – jemeshsu May 06 '12 at 10:56
  • 1
    For those, who use this: also consider: http://stackoverflow.com/questions/7095703/binding-view-based-nsoutlineview-to-core-data – bijan May 30 '12 at 23:13
  • Is this way can run in Leopard and Snow Leopard also ? – Alfian Busyro Aug 03 '12 at 07:56
  • View-based `NSTableView`s and `NSOutlineView`s were new to Lion, so I'm guessing they won't work on Leo and SnowLeo. Cell-based `NSTableView`s and `NSOutlineView`s would work, although the steps to create them and bind them would be somewhat different. – ipmcc Aug 03 '12 at 12:25
  • This is not working in latest xcode/lion for me, i get empty texts/headers (I only see the arrows & cell location). Any idea on what might be missing? – Olivier Aug 16 '12 at 16:14
  • This example didn't work for me in XCode 4.4/10.8 either. Just an empty view. Seems like the bindings have changed, or something is missing a step perhaps? –  Aug 30 '12 at 20:23
  • Tried this out again and saw something similar: Somehow, setting the value `children` for *Key Paths > Children* on the NSTreeController didn't "stick." I went back through all the settings, verifying that everything was as specified, and after fixing that one entry, and verifying that everything else had stuck, everything worked as expected. HTH. – ipmcc Sep 04 '12 at 16:50
  • 1
    @Olivier I had the same problem, the answer is here: http://stackoverflow.com/questions/7095703/binding-view-based-nsoutlineview-to-core-data. Kind of an old post but hope this helps someone out! – TheNextman Nov 15 '12 at 15:56
  • Dear ipmcc, I have a question, is possible with your data model use custom object? I don't understand how can I use for example 3 different object with different properties, on the table cell view I need different model key path not the same, in this case I need to add code? – francesco.venica Nov 13 '13 at 08:57
  • This would easily be worth its own question, so consider asking that way. In short: generally speaking, bindings are "declarative" in that you "declare" the property they're going to bind to at design time and it doesn't change. IME, it's easier to write a "shim" (categories or whatever) that makes all 3 classes expose the same properties than it is to change key paths at run time. Strictly speaking, it *is* possible to create and destroy bindings at runtime, but I've found it to be more trouble than its worth. (i.e. if you're writing code, just achieve your goals in code, without bindings) – ipmcc Nov 13 '13 at 11:39
  • If you're still getting the correct number of blank cells, just adopt the protocol in your view controller and hook up your NSOutlineView's delegate in IB. Then add this codesnippet to your view controller: - (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item { return [outlineView makeViewWithIdentifier:@"DataCell" owner:self]; } The DataCell is one of the default placeholders in an NSOutlineView. While you bind the value of the Table View Cell this comes from Table Cell View. – self.name Dec 30 '14 at 22:27
  • How can I do this with a storyboard instead of a .xib? Like, where do I put the TreeController? – Zac Jun 24 '17 at 21:52
5

Take a look at this example.

SideBarDemo

Cory
  • 2,302
  • 20
  • 33