Два самых простых примера использования NSOutlineView

Очень простой пример

NSOutlineView наследник NSTableView, используется для представления иерархических данных, которые могут сворачиваться и развертываться подобно файлам и папкам в ФС. Если удалить все уровни в NSOutlineView то получится просто NSTableView с одним столбцом и количеством строк с количеством строк равным количеству узлов в дереве.

Data source - это класс удовлетворяющий протоколу предоставления данных. Элементы (items) которые предоставляет data source должны быть объектами унаследованными от NSObject.




//
//  AppDelegate.h
//

#import <Cocoa/Cocoa.h>

@interface AppDelegate : NSObject <NSApplicationDelegate, NSOutlineViewDataSource> {
    IBOutlet NSOutlineView *myOutlineView;
    NSArray *dataSource;
}

@property (assign) IBOutlet NSWindow *window;


@end

В методе awakeFromNib инициализируется источник данных. Вы можете захотеть сделать это в методе applicationDidFinishLaunching протокола NSApplicationDelegate, но тогда ничего не будет отображаться в  NSOutlineView.

В первую очередь нужно реализовать методы удовлетворяющие протоколу NSOutlineViewDataSource:
  • - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
  • - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
  • - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
  • - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item


//
//  AppDelegate.m
//

#import "AppDelegate.h"

@implementation AppDelegate

@synthesize window = _window;


- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
    //initialize stuff
}

- (void)awakeFromNib
{
    dataSource = [NSArray arrayWithObjects:@"John", @"Mary", @"George", @"James", @"Niagara", @"Kathryn", nil];
    
#if DEBUG
    NSLog(@"%@", dataSource);
#endif
    
#if DEBUG
    NSLog(@"nib awaken");
#endif
}

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

#pragma mark -
#pragma mark OutlineView Data Source Delegate Methods



// -------------------------------------------------------------------------------
//  – outlineView(OBJECT):child(NSInteger):ofItem:(id)
//  Returns the object that will be displayed in the tree
// -------------------------------------------------------------------------------
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
    
    /*
     Здесь outline view спрашивает вас о потомке заданного item.
     
     If item is nil it is asking for a child of the root of the tree. You have only one top level item - the first element of the array, do if item is nil, you return [dataArray objectAtIndex: 0].
     
     If the item is the top level item i.e. if item == [dataArray objectAtIndex: 0], you need to return one of its children. Which one you return depends on index, naturally.
     */
    
    /*
     
     if (item == nil) {
     return [dataSource objectAtIndex:index];
     } else {
     return [dataSource objectAtIndex:0];
     }
     
     */
    
    return [dataSource objectAtIndex:index];
    //return item;
    
}// end – outlineView(OBJECT):child(NSInteger):ofItem:(id)

// -------------------------------------------------------------------------------
//  – outlineView(OBJECT):child(NSInteger):ofItem:(id)
// -------------------------------------------------------------------------------
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
    //Here the outline view is asking you if an item is expandable. You need to return YES for the top level item, NO for the others.
    /*
     if (item == nil) {
     return YES;
     } else {
     return NO;
     }
     */
    //return YES;
    if (item == nil) {
        return YES;
    }
    
    return NO;
    
}// end



// -------------------------------------------------------------------------------
//  – outlineView(OBJECT):child(NSInteger):ofItem:(id)
// -------------------------------------------------------------------------------
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
    /*
     Здесь outline view спрашивает количество потомков заданного item. Если значение item равно nil, то надо вернуть 1 (корень имеет одного потомка, your top level item). If the passed in item is your top level item, you should return 4 which is the number of items below it. В остальных случаях надо вернуть 0.
     */
    /*
     if (item == nil) {
     return [dataSource count];
     } else {
     return 0;
     }
     */
    
    return [dataSource count];
    
}// end



// -------------------------------------------------------------------------------
//  – outlineView(OBJECT):child(NSInteger):ofItem:(id)
// -------------------------------------------------------------------------------
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
    /*
     Здесь outline view запрашивает значение для отображения в заданном column для заданного item. If you have only one column and your array contains strings, you can just return the passed in item.
     */
    /*
     NSString *result = nil;
     
     if (item == nil) {
     result = nil;
     } else {
     result = item;
     }
     
     return result;
     */
    //return @"boo";
    
    if ([[tableColumn identifier] isEqualToString:@"fColumn"]) {
        /*
         if (item == nil) {
         return @"My family";
         }
         */
        return item == nil ? @"My family" : item;
    }
    
    if ([[tableColumn identifier] isEqualToString:@"sColumn"]) {
        //return item;
        return nil;
    }
    
    
    return item;
    
}// end

@end

Директивы:
#pragma mark -
#pragma mark OutlineView Data Source Delegate Methods
улучшают навигацию по коду.

Более сложный пример


Создадим OutlineViewController удовлетворяющий протоколам NSOutlineViewDataSource и NSOutlineViewDelegate.

//
//  OutlineViewController.h
//

#import <Cocoa/Cocoa.h>
#import "SecondOutlineViewDataSource.h"

@interface OutlineViewController : NSOutlineView <NSOutlineViewDataSource, NSOutlineViewDelegate> {
    NSDictionary *firstParent;
    NSDictionary *secondParent;
    NSArray *list;
    SecondOutlineViewDataSource *secondSource;
}
@property (nonatomic, retain) IBOutlet NSOutlineView *myOutlineView;
@property (nonatomic, retain) IBOutlet NSButton *firstCheckbox;
@property (nonatomic, retain) IBOutlet NSButton *secondCheckbox;

- (IBAction)checkState:(id)sender;


@end

Оба словаря инициализируются в методе init и осовобождаются из память в методу dealloc.

//
//  OutlineViewController.m
//
//  Instruments:
//  http://developer.apple.com/library/ios/#documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/UsingtheTraceDocument/UsingtheTraceDocument.html
//  http://disanji.net/iOS_Doc/#documentation/DeveloperTools/Conceptual/XcodeDebugging/220-Viewing_Variables_and_Memory/variables_and_memory.html

#import "OutlineViewController.h"

static NSUserDefaults *ud1 = nil;
//static NSUserDefaults *ud = nil;

@interface CheckForJames : NSObject

- (BOOL)_checkForJames:(id)item;

@end

@implementation CheckForJames

- (BOOL)_checkForJames:(id)item
{
    if ([item isKindOfClass:[NSDictionary class]]) {
        return [[item objectForKey:@"parent"] isEqualToString:@"James"] ? YES : NO;
    }
    return YES;
}

@end

@implementation OutlineViewController

/**
 * example on how to use +(void)initialize
 */
+ (void)initialize
{
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        ud1 = [NSUserDefaults standardUserDefaults];
    }
}

- (id)init
{
    if (self = [super init]) {
        // use ud if you want to initialize via init
        // ud = [NSUserDefaults standardUserDefaults];
        firstParent = [[NSDictionary alloc] initWithObjectsAndKeys:@"James",@"parent",[NSArray arrayWithObjects:@"Mary",@"Charlie", nil],@"children", nil];
        
        secondParent = [[NSDictionary alloc] initWithObjectsAndKeys:@"Elisabeth",@"parent",[NSArray arrayWithObjects:@"Jimmie",@"Kate", nil],@"children", nil];
        
        list = [[NSArray arrayWithObjects:firstParent,secondParent, nil] retain];
    }
    return self;
}

- (void)awakeFromNib
{
    DLog(@"%@", list);
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"firstCheckbox"]) {
        DLog(@"observed: %@", keyPath);
    }
}

#pragma mark -
#pragma mark NSOutlineView data source delegate methods

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
    if ([item isKindOfClass:[NSDictionary class]] || [item isKindOfClass:[NSArray class]]) {
        return YES;
    }else {
        return NO;
    }
}

- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
    
    if (item == nil) { //item is nil when the outline view wants to inquire for root level items
        return [list count];
    }
    
    if ([item isKindOfClass:[NSDictionary class]]) {
        return [[item objectForKey:@"children"] count];
    }
    
    return 0;
}

- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
    
    if (item == nil) { //item is nil when the outline view wants to inquire for root level items
        return [list objectAtIndex:index];
    }
    
    if ([item isKindOfClass:[NSDictionary class]]) {
        return [[item objectForKey:@"children"] objectAtIndex:index];
    }
    
    return nil;
}

- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)theColumn byItem:(id)item
{
    
    if ([[theColumn identifier] isEqualToString:@"children"]) {
        if ([item isKindOfClass:[NSDictionary class]]) {
            return [NSString stringWithFormat:@"%li kids",[[item objectForKey:@"children"] count]];
            
        }
        return item;
    } else {
        if ([item isKindOfClass:[NSDictionary class]]) {
            return [item objectForKey:@"parent"];
        }
    }
    
    return nil;
}

- (IBAction)checkState:(id)sender
{
    DLog(@"Checkbox state: %@", [sender state] == 0 ? @"NO" : @"YES");
    
}


- (BOOL)allowJamesToExpand
{
    return (BOOL)[_firstCheckbox state];
}

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item
{
    NSLog(@"%@", [ud1 boolForKey:@"kFirstCheckbox"] ? @"YES" : @"NO");
    NSLog(@"%@", [ud1 boolForKey:@"kSecondCheckbox"] ? @"YES" : @"NO");
    
    if ([[item objectForKey:@"parent"] isEqualToString:@"James"] && [ud1 boolForKey:@"kFirstCheckbox"])
        return YES;
    
    if ([[item objectForKey:@"parent"] isEqualToString:@"Elisabeth"] && [ud1 boolForKey:@"kSecondCheckbox"])
        return YES;
    
    return NO;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldCollapseItem:(id)item
{
    return YES;
}

- (void)dealloc
{
    [firstParent release];
    [secondParent release];
    [secondSource release];
    [super dealloc];
}

@end

Остальные исходные файлы:


//
//  SecondOutlineViewDataSource.h
//

#import <Foundation/Foundation.h>

@interface SecondOutlineViewDataSource : NSObject <NSOutlineViewDelegate, NSOutlineViewDataSource> {
    NSDictionary *firstParent;
    NSDictionary *secondParent;
    NSArray *list;
}

@property (nonatomic, retain) IBOutlet NSOutlineView *myOutlineView;

@end

//
//  SecondOutlineViewDataSource.m
//

#import "SecondOutlineViewDataSource.h"

static NSUserDefaults *ud1 = nil;
//static NSUserDefaults *ud;

@implementation SecondOutlineViewDataSource

/**
 * example on how to use +(void)initialize
 */
+ (void)initialize
{
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        ud1 = [NSUserDefaults standardUserDefaults];
    }
}

- (id)init
{
    if (self = [super init]) {
        // use ud if you want to initialize via init
        // ud = [NSUserDefaults standardUserDefaults];
        firstParent = [[[NSDictionary alloc] initWithObjectsAndKeys:@"Mac",@"computers",[NSArray arrayWithObjects:@"eMac",@"iMac", @"MacBook", @"iMac", @"MacBook Pro", nil],@"children", nil] retain];
        
        secondParent = [[[NSDictionary alloc] initWithObjectsAndKeys:@"PC",@"computers",[NSArray arrayWithObjects:@"Toshiba", @"Lenovo", @"DELL", @"HP", nil],@"children", nil] retain];
        
        list = [[NSArray arrayWithObjects:firstParent,secondParent, nil] retain];
        
    }
    return self;
}

- (void)awakeFromNib
{
    DLog(@"%@", list);
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
    if ([item isKindOfClass:[NSDictionary class]] || [item isKindOfClass:[NSArray class]]) {
        [firstParent release];
        return YES;
    }else {
        return NO;
    }
}


- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
    if (item == nil) { //item is nil when the outline view wants to inquire for root level items
        return [list count];
    }
    
    if ([item isKindOfClass:[NSDictionary class]]) {
        return [[item objectForKey:@"children"] count];
    }
    
    return 0;
}

- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
    if (item == nil) { //item is nil when the outline view wants to inquire for root level items
        return [list objectAtIndex:index];
    }
    
    if ([item isKindOfClass:[NSDictionary class]]) {
        return [[item objectForKey:@"children"] objectAtIndex:index];
    }
    
    return nil;
}

- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item

{
    if ([[tableColumn identifier] isEqualToString:@"children"]) {
        if ([item isKindOfClass:[NSDictionary class]]) {
            return [NSString stringWithFormat:@"%li computers",[[item objectForKey:@"children"] count]];
            
        }
        return item;
    } else {
        if ([item isKindOfClass:[NSDictionary class]]) {
            return [item objectForKey:@"computers"];
        }
    }
    
    return nil;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item
{
    DLog(@"%@", [ud1 boolForKey:@"kFirstCheckbox"] ? @"YES" : @"NO");
    DLog(@"%@", [ud1 boolForKey:@"kSecondCheckbox"] ? @"YES" : @"NO");
    
    if ([[item objectForKey:@"computers"] isEqualToString:@"Mac"] && [ud1 boolForKey:@"kFirstCheckbox"])
        return YES;
    
    if ([[item objectForKey:@"computers"] isEqualToString:@"PC"] && [ud1 boolForKey:@"kSecondCheckbox"])
        return YES;
    
    return NO;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldCollapseItem:(id)item
{
    return YES;
}

- (void)dealloc
{
    [firstParent release];
    [secondParent release];
    [list release];
    [super dealloc];
}

@end

//
//  AppDelegate.h
//

#import <Cocoa/Cocoa.h>
#import "OutlineViewController.h"
#import "SecondOutlineViewDataSource.h"

@interface AppDelegate : NSObject <NSApplicationDelegate> {
    id<NSOutlineViewDataSource> oldDataSource;
    id<NSOutlineViewDelegate> oldDelegate;
}

@property (assign) IBOutlet NSWindow *window;
@property (nonatomic, retain) IBOutlet NSOutlineView *myOutlineView;
@property (nonatomic, retain) IBOutlet NSButton *firstCheckbox;
@property (nonatomic, retain) IBOutlet NSButton *secondCheckbox;
@property (nonatomic, retain) IBOutlet NSButton *changeDelegate;


- (IBAction)toggleDataSource:(id)sender;

- (void)setNewDelegate:(id<NSOutlineViewDelegate>)newDelegate;
- (id<NSOutlineViewDelegate>)getNewDelegate;

- (void)setNewDataSource:(id<NSOutlineViewDataSource>)newDataSource;
- (id<NSOutlineViewDataSource>)getNewDataSource;

@end

//
//  AppDelegate.m
//

#import "AppDelegate.h"

static NSUserDefaults *ud = nil;

@implementation AppDelegate

OutlineViewController *ovc;
SecondOutlineViewDataSource *sovc;

@synthesize window = _window;
@synthesize myOutlineView = _myOutlineView;

- (id)init
{
    if (self = [super init]) {
        ovc = [[OutlineViewController alloc] init];
        ud = [NSUserDefaults standardUserDefaults];
        [self setNewDataSource:(id<NSOutlineViewDataSource>)ovc];
        [self setNewDelegate:(id<NSOutlineViewDelegate>)ovc];
    }
    
    return self;
}


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

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

}

- (void)setNewDelegate:(id<NSOutlineViewDelegate>)newDelegate
{
    if (oldDelegate != newDelegate) {
        if (oldDelegate)
            [oldDelegate release];
        oldDelegate = [newDelegate retain];
    }
}

- (id<NSOutlineViewDelegate>)getNewDelegate
{
    return oldDelegate;
}

- (void)setNewDataSource:(id<NSOutlineViewDataSource>)newDataSource
{
    if (oldDataSource != newDataSource) {
        if (oldDataSource)
            [oldDataSource release];
        oldDataSource = [newDataSource retain];
    }
}

- (id<NSOutlineViewDataSource>)getNewDataSource
{
    return oldDataSource;
}

- (void)awakeFromNib
{
    [_myOutlineView setDataSource:[self getNewDataSource]];
    [_myOutlineView setDelegate:[self getNewDelegate]];
    
    [_firstCheckbox setTitle:@"Expand/ Collapse James Node"];
    [_secondCheckbox setTitle:@"Expand/ Collapse Elisabeth Node"];
    
    [_changeDelegate setTitle:@"Second Data Source"];
    [_changeDelegate sizeToFit];    
    
    [AppDelegate addObserver:_myOutlineView forKeyPath:@"firstCheckbox" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
}

- (IBAction)toggleDataSource:(id)sender
{
    [_myOutlineView setDataSource:nil];
    [_myOutlineView setDelegate:nil];
    
   
#ifdef DEBUG
    NSLog(@"Data Source toggle");
#endif
    
    if ([sender state] == NSOnState) {
        [_firstCheckbox setTitle:@"Expand/ Collapse Mac Node"];
        [_secondCheckbox setTitle:@"Expand/ Collapse PC Node"];
        [_changeDelegate setTitle:@"First Data Source"];
        [_changeDelegate sizeToFit];        
        sovc = [[SecondOutlineViewDataSource alloc] init];
        [self setNewDelegate:(id<NSOutlineViewDelegate>)sovc];
        [self setNewDataSource:(id<NSOutlineViewDataSource>)sovc];
        //[_myOutlineView setDelegate:[self getNewDelegate]];
        //[_myOutlineView setDataSource:[self getNewDataSource]];
        [_myOutlineView setDelegate:(id<NSOutlineViewDelegate>)sovc];
        [_myOutlineView setDataSource:(id<NSOutlineViewDataSource>)sovc];

        [_myOutlineView reloadData];
        
#ifdef DEBUG
        NSLog(@"On state with source: %@", [[_myOutlineView dataSource] description]);
#endif


    } else {
        [_firstCheckbox setTitle:@"Expand James Node"];
        [_secondCheckbox setTitle:@"Expand Elisabeth Node"];
        [_changeDelegate setTitle:@"Second Data Source"];
        [_changeDelegate sizeToFit];
        ovc = [[OutlineViewController alloc] init];
        [self setNewDelegate:(id<NSOutlineViewDelegate>)ovc];
        [self setNewDataSource:(id<NSOutlineViewDataSource>)ovc];
        //[_myOutlineView setDelegate:[self getNewDelegate]];
        //[_myOutlineView setDataSource:[self getNewDataSource]];
        [_myOutlineView setDelegate:(id<NSOutlineViewDelegate>)ovc];
        [_myOutlineView setDataSource:(id<NSOutlineViewDataSource>)ovc];
        [_myOutlineView reloadData];
        
        

#ifdef DEBUG
        NSLog(@"Off state with source: %@", [[_myOutlineView dataSource] description]);
#endif
    }
}

@end

--