树形控件在多列列表、多级菜单中使用比较常见,好比:国家-省份-城市 多级选择、学校-专业-班级 多级选择等等。然而IOS自带控件中并不存在树形控件,咱们要在IOS开发中使用树形控件,一般须要本身扩展UITableView列表控件。
如今在这里开源一个本身写的高扩展性,高复用性的IOS树形结构控件。
支持无限极树形结构。
使用的是非递归方式。
代码简单易懂,扩展方便。
图片演示以下:node
parentId : 该节点的父控件id号,若是为-1则表示该节点为根节点
nodeId : 每一个节点自身的id号,是每一个节点的惟一标示
name : 节点的名称
depth : 该节点所带的树形结构中的深度,根节点的深度为0
expand : 该节点是否处于展开状态面试
1 /** 2 * 每一个节点类型 3 */ 4 @interface Node : NSObject 5 6 @property (nonatomic , assign) int parentId;//父节点的id,若是为-1表示该节点为根节点 7 8 @property (nonatomic , assign) int nodeId;//本节点的id 9 10 @property (nonatomic , strong) NSString *name;//本节点的名称 11 12 @property (nonatomic , assign) int depth;//该节点的深度 13 14 @property (nonatomic , assign) BOOL expand;//该节点是否处于展开状态 15 16 /** 17 *快速实例化该对象模型 18 */ 19 - (instancetype)initWithParentId : (int)parentId nodeId : (int)nodeId name : (NSString *)name depth : (int)depth expand : (BOOL)expand; 20 21 @end
1 //----------------------------------中国的省地市关系图3,2,1-------------------------------------------- 2 Node *country1 = [[Node alloc] initWithParentId:-1 nodeId:0 name:@"中国" depth:0 expand:YES]; 3 Node *province1 = [[Node alloc] initWithParentId:0 nodeId:1 name:@"江苏" depth:1 expand:NO]; 4 Node *city1 = [[Node alloc] initWithParentId:1 nodeId:2 name:@"南通" depth:2 expand:NO]; 5 Node *city2 = [[Node alloc] initWithParentId:1 nodeId:3 name:@"南京" depth:2 expand:NO]; 6 Node *city3 = [[Node alloc] initWithParentId:1 nodeId:4 name:@"苏州" depth:2 expand:NO]; 7 Node *province2 = [[Node alloc] initWithParentId:0 nodeId:5 name:@"广东" depth:1 expand:NO]; 8 Node *city4 = [[Node alloc] initWithParentId:5 nodeId:6 name:@"深圳" depth:2 expand:NO]; 9 Node *city5 = [[Node alloc] initWithParentId:5 nodeId:7 name:@"广州" depth:2 expand:NO]; 10 Node *province3 = [[Node alloc] initWithParentId:0 nodeId:8 name:@"浙江" depth:1 expand:NO]; 11 Node *city6 = [[Node alloc] initWithParentId:8 nodeId:9 name:@"杭州" depth:2 expand:NO]; 12 //----------------------------------美国的省地市关系图0,1,2-------------------------------------------- 13 Node *country2 = [[Node alloc] initWithParentId:-1 nodeId:10 name:@"美国" depth:0 expand:YES]; 14 Node *province4 = [[Node alloc] initWithParentId:10 nodeId:11 name:@"纽约州" depth:1 expand:NO]; 15 Node *province5 = [[Node alloc] initWithParentId:10 nodeId:12 name:@"德州" depth:1 expand:NO]; 16 Node *city7 = [[Node alloc] initWithParentId:12 nodeId:13 name:@"休斯顿" depth:2 expand:NO]; 17 Node *province6 = [[Node alloc] initWithParentId:10 nodeId:14 name:@"加州" depth:1 expand:NO]; 18 Node *city8 = [[Node alloc] initWithParentId:14 nodeId:15 name:@"洛杉矶" depth:2 expand:NO]; 19 Node *city9 = [[Node alloc] initWithParentId:14 nodeId:16 name:@"旧金山" depth:2 expand:NO]; 20 21 //----------------------------------日本的省地市关系图0,1,2-------------------------------------------- 22 Node *country3 = [[Node alloc] initWithParentId:-1 nodeId:17 name:@"日本" depth:0 expand:YES]; 23 NSArray *data = [NSArray arrayWithObjects:country1,province1,city1,city2,city3,province2,city4,city5,province3,city6,country2,province4,province5,city7,province6,city8,city9,country3, nil];
1 TreeTableView *tableview = [[TreeTableView alloc] initWithFrame:CGRectMake(0, 20, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)-20) withData:data]; 2 [self.view addSubview:tableview];
经过简单以上三步,你就能够把该树形控件集成到你的项目中。网络
树形结构的列表用的其实就是UITableView控件,可是如何可以让UItableView可以动态的增长和删除指定的行数的cell是实现树形结构的关键所在。
这时候咱们须要用到两个UItableView自带的行数:app
1 - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; 2 - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
第一个函数用来在指定的位置插入cells,第二个函数用来在指定的位置删除cells,而且这二个函数都自带多种动画效果,让删除和插入的过程不至于太突兀、有种渐变的感受,具备良好的用户体验。
对于这几个动画作了尝试:
UITableViewRowAnimationFade : 渐变效果
UITableViewRowAnimationRight : 右边进入,右边消失
UITableViewRowAnimationLeft : 左边进入,左边消失
UITableViewRowAnimationTop : 顶部进入,顶部消失
UITableViewRowAnimationBottom : 顶部进入,底部消失函数
注意点:
学习
在调用insertRowsAtIndexPaths和deleteRowsAtIndexPaths的时候必定要先改变数据源,在调用上述函数,否则会产生crash。动画
接下来把TreeTableView的主要代码展现出来,由于原本代码量就不大,并且代码中注释也比较全,但愿可以帮助你们理解。ui
1 #import "TreeTableView.h" 2 #import "Node.h" 3 4 @interface TreeTableView ()<UITableViewDataSource,UITableViewDelegate> 5 6 @property (nonatomic , strong) NSArray *data;//传递过来已经组织好的数据(全量数据) 7 8 @property (nonatomic , strong) NSMutableArray *tempData;//用于存储数据源(部分数据) 9 10 11 @end 12 13 @implementation TreeTableView 14 15 -(instancetype)initWithFrame:(CGRect)frame withData : (NSArray *)data{ 16 self = [super initWithFrame:frame style:UITableViewStyleGrouped]; 17 if (self) { 18 self.dataSource = self; 19 self.delegate = self; 20 _data = data; 21 _tempData = [self createTempData:data]; 22 } 23 return self; 24 } 25 26 /** 27 * 初始化数据源 28 */ 29 -(NSMutableArray *)createTempData : (NSArray *)data{ 30 NSMutableArray *tempArray = [NSMutableArray array]; 31 for (int i=0; i<data.count; i++) { 32 Node *node = [_data objectAtIndex:i]; 33 if (node.expand) { 34 [tempArray addObject:node]; 35 } 36 } 37 return tempArray; 38 } 39 40 41 #pragma mark - UITableViewDataSource 42 43 #pragma mark - Required 44 45 -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ 46 return _tempData.count; 47 } 48 49 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ 50 static NSString *NODE_CELL_ID = @"node_cell_id"; 51 52 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NODE_CELL_ID]; 53 if (!cell) { 54 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:NODE_CELL_ID]; 55 } 56 57 Node *node = [_tempData objectAtIndex:indexPath.row]; 58 59 NSMutableString *name = [NSMutableString string]; 60 for (int i=0; i<node.depth; i++) { 61 [name appendString:@" "]; 62 } 63 [name appendString:node.name]; 64 65 cell.textLabel.text = name; 66 67 return cell; 68 } 69 70 71 #pragma mark - Optional 72 - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{ 73 return 0.01; 74 } 75 76 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ 77 return 40; 78 } 79 80 - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{ 81 return 0.01; 82 } 83 84 #pragma mark - UITableViewDelegate 85 86 #pragma mark - Optional 87 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ 88 //先修改数据源 89 Node *parentNode = [_tempData objectAtIndex:indexPath.row]; 90 NSUInteger startPosition = indexPath.row+1; 91 NSUInteger endPosition = startPosition; 92 BOOL expand = NO; 93 for (int i=0; i<_data.count; i++) { 94 Node *node = [_data objectAtIndex:i]; 95 if (node.parentId == parentNode.nodeId) { 96 node.expand = !node.expand; 97 if (node.expand) { 98 [_tempData insertObject:node atIndex:endPosition]; 99 expand = YES; 100 }else{ 101 expand = NO; 102 endPosition = [self removeAllNodesAtParentNode:parentNode]; 103 break; 104 } 105 endPosition++; 106 } 107 } 108 109 //得到须要修正的indexPath 110 NSMutableArray *indexPathArray = [NSMutableArray array]; 111 for (NSUInteger i=startPosition; i<endPosition; i++) { 112 NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:0]; 113 [indexPathArray addObject:tempIndexPath]; 114 } 115 116 //插入或者删除相关节点 117 if (expand) { 118 [self insertRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationNone]; 119 }else{ 120 [self deleteRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationNone]; 121 } 122 } 123 124 /** 125 * 删除该父节点下的全部子节点(包括孙子节点) 126 * 127 * @param parentNode 父节点 128 * 129 * @return 邻接父节点的位置距离该父节点的长度,也就是该父节点下面全部的子孙节点的数量 130 */ 131 -(NSUInteger)removeAllNodesAtParentNode : (Node *)parentNode{ 132 NSUInteger startPosition = [_tempData indexOfObject:parentNode]; 133 NSUInteger endPosition = startPosition; 134 for (NSUInteger i=startPosition+1; i<_tempData.count; i++) { 135 Node *node = [_tempData objectAtIndex:i]; 136 endPosition++; 137 if (node.depth == parentNode.depth) { 138 break; 139 } 140 node.expand = NO; 141 } 142 if (endPosition>startPosition) { 143 [_tempData removeObjectsInRange:NSMakeRange(startPosition+1, endPosition-startPosition-1)]; 144 } 145 return endPosition; 146 }
在演示项目中,每一个cell我都使用系统自带的cell,样式比较简单,若是你要展示更加漂亮的样式,能够自定义cell。
同时,你也能够扩展该数据模型,运动到更加复杂的业务处理中。好比如下场景:atom
Demo下载地址:这是一个个人iOS交流群:624212887,群文件自行下载,无论你是小白仍是大牛热烈欢迎进群 ,分享面试经验,讨论技术, 你们一块儿交流学习成长!但愿帮助开发者少走弯路。spa
若是以为对你还有些用,就关注小编+喜欢这一篇文章。你的支持是我继续的动力。
下篇文章预告:iOS开发UI篇--一个支持图文混排的ActionSheet
文章来源于网络,若有侵权,请联系小编删除。