文章目錄
一、簡述
NSOutlineView 繼承自 NSTableView, 比 NSTableView 多一個層級的概念,所以類似多層級樹狀的視圖一般使用這個類來實現。
在學習 NSOutlineView 之前,最好先學習 NSTableView 的實現。
二、一個簡單的 view-based Outlineview
這裏示例實現一個簡單的 view-based Outlineview
1、準備
@interface OutlineWindowController ()<NSOutlineViewDataSource, NSOutlineViewDelegate>
@property (nonatomic, strong) NSScrollView *scrollView;
@property (nonatomic, strong) NSOutlineView *outlineView;
@property (nonatomic, strong) NSArray *wholeTasks;
@end
2、數據準備
-(void)initData {
//1級根節點
TaskModel *model0 = [[TaskModel alloc] init];
model0.title = @"任務0";
model0.ID = @"0";
TaskModel *model1 = [[TaskModel alloc]init];
model1.title = @"任務1";
model1.ID = @"1";
TaskModel *model2 = [[TaskModel alloc]init];
model2.title = @"任務2";
model2.ID = @"2";
//2級節點
TaskModel *model10 = [[TaskModel alloc]init];
model10.title = @"任務1-0";
model10.ID = @"10";
TaskModel *model11 = [[TaskModel alloc]init];
model11.title = @"任務1-1";
model11.ID = @"11";
//3級節點
TaskModel *model100 = [[TaskModel alloc]init];
model100.ID = @"100";
model100.title = @"任務1-0-0";
TaskModel *model110 = [[TaskModel alloc]init];
model110.ID = @"110";
model110.title = @"任務1-1-0";
//設置子節點
self.wholeTasks = @[model0,model1,model2];
model1.subTaskArray = @[model10, model11];
model10.subTaskArray = @[model100];
model11.subTaskArray = @[model110];
}
3、創建視圖
- (void)testOutLine{
NSTableColumn *tableColumn = [[NSTableColumn alloc] init];
tableColumn.resizingMask = NSTableColumnAutoresizingMask;
NSOutlineView *outlineView = [[NSOutlineView alloc] init];
outlineView.allowsColumnResizing = YES;
outlineView.headerView = nil;
outlineView.columnAutoresizingStyle = NSTableViewFirstColumnOnlyAutoresizingStyle;
outlineView.usesAlternatingRowBackgroundColors = YES;//背景顏色的交替,一行白色,一行灰色。
[outlineView addTableColumn:tableColumn];
outlineView.delegate = self;
outlineView.dataSource = self;
outlineView.autosaveExpandedItems = YES;
/* */
NSScrollView *scrollView = [[NSScrollView alloc] initWithFrame:NSMakeRect(10, 10, 300, 250)];
scrollView.documentView = outlineView;
scrollView.hasVerticalScroller = YES;
scrollView.autohidesScrollers = YES;
self.scrollView = scrollView;
self.outlineView = outlineView;
[self.window.contentView addSubview:self.scrollView];
}
4、實現 datasource protocol
// 每一層級節點包含的下一級節點的數量。
-(NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
NSInteger num = 0;
if (!item) {
num = self.wholeTasks.count;
NSLog(@"root num : %ld",(long)num);
}else{
TaskModel *model = (TaskModel *)item;
num = model.subTaskArray.count;
NSLog(@"%@ - num : %ld",model.ID,(long)num);
}
return num;
}
/*
item 爲nil空時表示獲取頂級節點模型。
每一層級節點的模型對象爲item時,根據 item 獲取子節點模型。
這個地方用於向自定義的cellView傳遞數據,如果return item.title,則那邊接收到的“self.objectValue”就是item.title。
*/
-(id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
TaskModel *childModel;
if(item) {
TaskModel *model = (TaskModel *)item;
childModel = model.subTaskArray[index];
}else{
childModel = self.wholeTasks[index];
}
NSLog(@"index : %ld , item : %@ , childModel : %@",index,item,childModel.ID);
return childModel;
}
// 節點是否可以 展開/摺疊。
-(BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
//count 大於0表示有子節點,需要允許Expandable
if(!item) {
return [self.wholeTasks count] > 0 ;
}else {
TaskModel *model = (TaskModel *)item;
return [model.subTaskArray count] > 0;
}
}
PS:
- 代理方法中的 item 代表該項要使用的 item, 如果爲空代表這是 root 節點,返回我們數據源第一層的數據即可。這個在大部分的方法中都會出現,可以多體會它的意思。
outlineView:child:ofItem:
需要返回該節點的子 item;如果返回了該 item, 程序可能會進入死循環;outlineView:objectValueForTableColumn:byItem:
對於 view-based 是可選,對於 cell-based 是必須實現。
5、實現 delegate protocol
// 是否是組元素
- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
if (!item) {
return self.wholeTasks.count;
}
TaskModel *model = (TaskModel *)item;
return model.subTaskArray.count;
}
/*
view 視圖
和 NSTableView 中的方法 tableView:viewForTableColumn:row: 一樣;
*/
- (nullable NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(nullable NSTableColumn *)tableColumn item:(id)item{
CustomTableCellView *cell = [CustomTableCellView cellWithTableView:outlineView owner:self];
NSLog(@"item : %@",item);
TaskModel *model = (TaskModel *)item;
cell.titleLabel.stringValue = model.ID;
return cell;
}
/*
rowView 視圖
*/
-(NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item{
CustomTableRowView *rowView = [CustomTableRowView rowViewWithTableView:outlineView];
// [rowView msSetLayerColor:[NSColor orangeColor]];
return rowView;
}
// 自定義行高
- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item{
return 30;
}
// 選擇節點後的通知
- (void)outlineViewSelectionDidChange:(NSNotification *)notification{
NSLog(@"--");
NSOutlineView *outlineView = notification.object;
NSInteger row = [outlineView selectedRow];
TaskModel *model = (TaskModel *)[outlineView itemAtRow:row];
NSLog(@"name = %@",model.title);
}
6、其他類的實現
item 的類:TaskModel
@interface TaskModel : NSObject
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *ID;
@property (nonatomic, strong) NSArray *subTaskArray;
@end
自定義 cellView
@interface CustomTableCellView : NSTableCellView
@property (nonatomic, strong) NSTextField *titleLabel;
+ (instancetype)cellWithTableView:(NSTableView *)tableView owner:(nullable id)owner;
@end
@implementation CustomTableCellView
static NSString * CustomTableCellID = @"CustomTableCellID";
+ (instancetype)cellWithTableView:(NSTableView *)tableView owner:(nullable id)owner{
CustomTableCellView *cell = [tableView makeViewWithIdentifier:CustomTableCellID owner:owner];
if (!cell) {
cell = [[CustomTableCellView alloc]init];
cell.identifier = CustomTableCellID;
[cell setUpViews];
}
return cell;
}
- (void)setUpViews{
NSTextField *titleLabel = [[NSTextField alloc]initWithFrame:NSMakeRect(0, 0, 200, 30)];
[self addSubview:titleLabel];
titleLabel.stringValue = @"123";
titleLabel.editable = NO;
titleLabel.bordered = NO;
self.titleLabel = titleLabel;
self.wantsLayer = YES;
// self.layer.backgroundColor = [NSColor yellowColor].CGColor;
self.layer.backgroundColor = [NSColor whiteColor].CGColor;
}
@end
自定義 rowView
@interface CustomTableRowView : NSTableRowView
+ (instancetype)rowViewWithTableView:(NSTableView *)tableView;
@end
@implementation CustomTableRowView
static NSString * customTableRowViewID = @"customTableRowViewID";
+ (instancetype)rowViewWithTableView:(NSTableView *)tableView{
CustomTableRowView *rowView = [tableView makeViewWithIdentifier:customTableRowViewID owner:self];
if (rowView == nil) {
rowView = [[CustomTableRowView alloc] init];
rowView.identifier = customTableRowViewID;
// [rowView setUpViews];
}
return rowView;
}
//自定義 row 背景色
- (void)setBackgroundColor:(NSColor *)backgroundColor {
super.backgroundColor = [NSColor whiteColor];
// super.backgroundColor = [NSColor orangeColor];
}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
}
// 自定義 row 被選中的背景色
-(void)drawSelectionInRect:(NSRect)dirtyRect {
if (self.selectionHighlightStyle != NSTableViewSelectionHighlightStyleNone) {
[[NSColor lightGrayColor] setFill];
NSBezierPath *path = [NSBezierPath bezierPathWithRect:NSInsetRect(self.bounds, 0, 0)];
[path fill];
[path stroke];
}
}
@end
其它問題:
- 如何一進入就展開?
- 如果設置縮進?
- 如何設置遞進的圖標?