Очень простой пример
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
--