macOS development - NSOutlineView simple implementation (pure code)


A brief

NSOutlineView inherited from NSTableView, a multi-level concept than NSTableView, so similar to the multi-level tree-view generally use this class to achieve.
Before learning NSOutlineView, better learn NSTableView implementation.


Second, a simple view-based Outlineview

Here a simple example of an implementation of the view-based Outlineview
Here Insert Picture Description


1, ready


@interface OutlineWindowController ()<NSOutlineViewDataSource, NSOutlineViewDelegate>

@property (nonatomic, strong) NSScrollView *scrollView;
@property (nonatomic, strong) NSOutlineView *outlineView;

@property (nonatomic, strong) NSArray *wholeTasks;

@end
  

2, data preparation


-(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, create a view


- (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, to achieve 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 on behalf of the proxy methods to be used, if it is representative of an empty root node, it returns the data of the first layer of the data source we can. This will appear in most ways, you can understand its meaning and more.
  • outlineView:child:ofItem: The need to return the child node of the item; if you return the item, the program may enter an infinite loop;
  • outlineView:objectValueForTableColumn:byItem: For view-based is optional for cell-based must be implemented.

5, to achieve 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, the realization of other classes

item classes: TaskModel

@interface TaskModel : NSObject
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *ID;
@property (nonatomic, strong) NSArray *subTaskArray;

@end

Custom 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

Custom 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

other questions:

  • How to enter a deployed?
  • If you set the indent?
  • How to set progressive icon?
Published 164 original articles · won praise 162 · Views 650,000 +

Guess you like

Origin blog.csdn.net/lovechris00/article/details/100624833
Recommended