iOS 树形结构菜单(参照以前大神写的博客)
2016-09-24 13:01
429 查看
说明:写的东西是参照某位大神写的博客,但是我自己的数据他有些没有,然后我就根据大神的博客重新写了下,希望对大家有用
1.数据的形式,请求下来的数据将会是如下格式
2.那么根据这个节点我们创建一个Model,代码如下,代码中我写好了注释
创建一个Node类继承于NSObject
Node.h中的代码:
Node.m中的代码
这样做完之后,我们就把数据的Model构建完成了,现在根据这个Model来创建树型菜单
3.创建一个TreeTableView类继承于UITableView,其中的注释在代码中,我就直接粘贴代码了
TreeTableView.h中的代码
TreeTableView.m中的代码
4.这两个类创建完成,在ViewController中的初始化并调用
ViewController.h代码
1.数据的形式,请求下来的数据将会是如下格式
@property (nonatomic, strong) NSString *itemParentId;//父节点的id @property (nonatomic, strong) NSString *itemId;//本节点的id @property (nonatomic, strong) NSString *itemName;//本节点的名称 @property (nonatomic, assign) BOOL expand;//该节点是否处于展开状态这些数据中有父节点的ID和自身的ID,还有自身的名字,并且自己写一个属性expand,之后来判断该节点是否处于展开或者收起状态
2.那么根据这个节点我们创建一个Model,代码如下,代码中我写好了注释
创建一个Node类继承于NSObject
Node.h中的代码:
#import <Foundation/Foundation.h> @interface Node : NSObject //这里是数据 @property (nonatomic, strong) NSString *itemParentId;//父节点的id @property (nonatomic, strong) NSString *itemId;//本节点的id @property (nonatomic, strong) NSString *itemName;//本节点的名称 @property (nonatomic, strong) NSString *itemIndex;//本节点在该级菜单中的索引值(这个值可以不用关注,这是我自己用到的数据) @property (nonatomic, assign) BOOL expand;//该节点是否处于展开状态 @property (nonatomic, strong) NSString *siteSSCID;//(这个值也不用关注,也是我自己的数据的东西) /** * 快速实例化该对象模型 * * @param itemParentId 父节点的id * @param itemId 本节点的id * @param itemName 本节点的名称 * @param itemIndex 本节点在该级菜单中的索引值 * @param expand 该节点是否处于展开状态 * * @return 一个node实例 */ - (instancetype)initWithParentId:(NSString *)itemParentId nodeId:(NSString *)itemId name:(NSString *)itemName index:(NSString *)itemIndex siteSSCID:(NSString *)itemSiteSSCID expand:(BOOL)expand; @end
Node.m中的代码
#import "Node.h" @implementation Node - (instancetype)initWithParentId:(NSString *)itemParentId nodeId:(NSString *)itemId name:(NSString *)itemName index:(NSString *)itemIndex siteSSCID:(NSString *)itemSiteSSCID expand:(BOOL)expand{ self = [self init]; if (self) { self.itemParentId = itemParentId; self.itemId = itemId; self.itemName = itemName; self.itemIndex = itemIndex; self.siteSSCID = itemSiteSSCID; self.expand = expand; } return self; } @end
这样做完之后,我们就把数据的Model构建完成了,现在根据这个Model来创建树型菜单
3.创建一个TreeTableView类继承于UITableView,其中的注释在代码中,我就直接粘贴代码了
TreeTableView.h中的代码
#import <UIKit/UIKit.h> @interface TreeTableView : UITableView @property (nonatomic , strong) NSArray *dataS;//传递过来已经组织好的数据(全量数据) @property (nonatomic , strong) NSMutableArray *tempData;//用于存储数据源(部分数据,这个数据是根据dataS来最初显示在界面上的菜单的名字) @property (nonatomic , strong) void (^selectBlock)(NSString *);//这个block是我用来传当点击到最后子节点的时候,具体的传值我还没写
-(NSMutableArray *)createTempData : (NSArray *)data;//初始化表格数据(这个方法是根据总数据先初始化最开显示的最上层的菜单,然后得到tempData) @end
TreeTableView.m中的代码
#import "TreeTableView.h" #import "Node.h" @interface TreeTableView ()<UITableViewDataSource,UITableViewDelegate> { NSMutableDictionary *_dic;//处理重复数据用 NSInteger _depth;//深度,就是是第几级菜单 } @end @implementation TreeTableView -(instancetype)initWithFrame:(CGRect)frame{ self = [super initWithFrame:frame style:UITableViewStyleGrouped]; if (self) { _dic = [@{} mutableCopy]; self.dataSource = self; self.delegate = self; } return self; } /** * 初始化数据源 */ -(NSMutableArray *)createTempData : (NSArray *)data{ NSMutableArray *tempArray = [@[] mutableCopy]; NSMutableArray ca7f *keys = [@[] mutableCopy]; for (int i=0; i<data.count; i++) { Node *node = [self.dataS objectAtIndex:i]; if ([node.itemParentId isEqualToString:@"0"]) { node.expand = YES; [_dic setObject:node forKey:node.itemName]; [keys addObject:node.itemName]; } } //处理初始化数据中重复(这里处理的是我自己数据中带有重复的数据) [keys enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { Node *node1 = tempArray.lastObject; Node *node2 = _dic[obj]; if (node1==nil || ![node1.itemName isEqualToString:node2.itemName]){ [tempArray addObject:_dic[obj]]; } }]; return tempArray; } #pragma mark *** UITableViewDelegate/UITableViewDataSource *** -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
//首先展示的数据是tempData的数据
return self.tempData.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ static NSString *NODE_CELL_ID = @"node_cell_id"; //定义cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NODE_CELL_ID]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:NODE_CELL_ID]; }
Node *node = [self.tempData objectAtIndex:indexPath.row];//取到数据中node的name _depth = 0;//全局变量,深度置为0 NSInteger tem = [self findDepthOfNode:node];//找到某个node在整个树形菜单的深度
</pre><pre code_snippet_id="1897900" snippet_file_name="blog_20160924_11_7136027" name="code" class="objc">//处理界面展示的层次关系,在前面加空格实现,也可以自定义cell,在前面加上imageView来更形象的展示,我就没有具体写了 NSMutableString *name = [NSMutableString string]; for (int i=0; i<tem; i++) { [name appendString:@" "]; } [name appendString:node.itemName];
cell.textLabel.font = [UIFont systemFontOfSize:15.0];//设置cell中字体的大小 cell.textLabel.text = name;//设置cell显示的内容 cell.backgroundColor = [UIColor brownColor];//设置cell的背景颜色 return cell; } //这个方法是用来找它的深度的。根据某个节点找到节点在整个菜单中深度(作用就是会在后面收起的时候做判断) - (NSInteger)findDepthOfNode:(Node *)node{ Node * nextNode;
//根据传过来的node判断是不是是根节点(我设置的根节点,就是没有父节点的节点的父节点ID为“0”) if (![node.itemParentId isEqualToString:@"0"]) {
//如果不是父节点,那么它的深度加一 _depth ++;
//然后去循环找视图展示的数据中,这个节点的父节点 for (int i = 0; i < self.tempData.count; i++) { nextNode = [self.tempData objectAtIndex:i];
//判断如果找到有这样一个父节点数据在tempData中那么就break if ([node.itemParentId isEqualToString:nextNode.itemId]) { break; } }
//用递归的思想循环找 [self findDepthOfNode:nextNode]; }
//最终获取到数据在tempData中的深度(也就是在第几层) return _depth; } - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{ return 0.01; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ return 40; } - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{ return 0.01; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ //这里是关键,就是点击展开或者收起的代码 BOOL expand = NO; NSUInteger startPosition = indexPath.row+1;//点击某一行,获取开始的位置 NSUInteger endPosition = startPosition;//结束的位置 Node *parentNode = [_tempData objectAtIndex:indexPath.row];//获取到某一行的节点
//这里可以先不用管,我还没有写具体的内容,这里是判断如果点击之后再无子节点,那么它的层级最低,可以将点击的东西传值到其他页面 if (self.selectBlock) { self.selectBlock(parentNode.itemName); }
//在总的数据dataS中查找
for (int i=0; i<self.dataS.count; i++) { Node *node = [self.dataS objectAtIndex:i]; Node *node2 = [_tempData objectAtIndex:endPosition-1]; //判断语句后面一个判断是来做数据重复使用的,但是在数据中 还有CONN_ID来区别同样的数据,可能以后需要这些重复的数据在界面上显示,那么久直接删除后面一个判断就可以
//判断语句前一个语句是查找所有数据,一个个查看它的父节点是不是点击的那个,是的话就插入数据到表格的相应位置,否则删除表格中相应的数据 if ([node.itemParentId isEqualToString:parentNode.itemId] && ![node2.itemId isEqualToString:node.itemId]) {
node.expand = !node.expand; if (node.expand) { [_tempData insertObject:node atIndex:endPosition]; expand = YES; }else{ expand = NO; endPosition = [self removeAllNodesAtParentNode:parentNode]; break; } endPosition++; } } //获得需要修正的indexPath NSMutableArray *indexPathArray = [NSMutableArray array]; for (NSUInteger i=startPosition; i<endPosition; i++) { NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:0]; [indexPathArray addObject:tempIndexPath]; } //插入或者删除相关节点 if (expand) { [self insertRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationNone]; }else{ [self deleteRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationNone]; } } /** * 删除该父节点下的所有子节点(包括孙子节点) * * @param parentNode 父节点 * * @return 邻接父节点的位置距离该父节点的长度,也就是该父节点下面所有的子孙节点的数量 */ -(NSUInteger)removeAllNodesAtParentNode : (Node *)parentNode{
NSUInteger startPosition = [_tempData indexOfObject:parentNode]; NSUInteger endPosition = startPosition + 1; _depth =0; NSInteger y = [self findDepthOfNode:parentNode]; for (NSUInteger i=startPosition+1; i<self.tempData.count; i++) { Node *node = [_tempData objectAtIndex:i]; _depth = 0; NSInteger x = [self findDepthOfNode:node]; // 判断节点深度是否大于等于父节点深度 if ( x <= y) { break; } endPosition++; node.expand = NO; } if (endPosition>startPosition) { [self.tempData removeObjectsInRange:NSMakeRange(startPosition+1, endPosition-startPosition-1)]; } return endPosition; } @end
4.这两个类创建完成,在ViewController中的初始化并调用
ViewController.h代码
#import <UIKit/UIKit.h> @interface ViewController : UIViewController @endViewController.m代码
#import "ViewController.h" #import "TreeTableView.h" #import "Node.h" @interface ViewController () @property (nonatomic,strong)TreeTableView *treeTableView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //初始化数据 self.treeTableView.dataS = [self LoadDataForTableView]; self.treeTableView.tempData = [self.treeTableView createTempData:self.treeTableView.dataS]; //加载视图 [self.view addSubview:self.treeTableView]; } #pragma mark *** Private Mathod *** - (NSArray *)LoadDataForTableView{ // 构造数据 // 构造总的数据 //我这里是以“0”作为最根节点,如果是其他作为根节点,要在代码中做相应的修改 Node *node = [[Node alloc]initWithParentId:@"0" nodeId:@"国家1" name:@"中国" index:@"这个数据是我自己的数据你要不要无所谓" siteSSCID:@"这个数据也是" expand:NO]; Node *node1 = [[Node alloc]initWithParentId:@"0" nodeId:@"国家2" name:@"俄罗斯" index:@"这个数据是我自己的数据你要不要无所谓" siteSSCID:@"这个数据也是" expand:NO]; //这里的ParentId一定是父节点的nodeId Node *node2 = [[Node alloc]initWithParentId:@"国家1" nodeId:@"省份1" name:@"四川" index:@"这个数据是我自己的数据你要不要无所谓" siteSSCID:@"这个数据也是" expand:NO]; Node *node3 = [[Node alloc]initWithParentId:@"国家1" nodeId:@"省份2" name:@"浙江" index:@"这个数据是我自己的数据你要不要无所谓" siteSSCID:@"这个数据也是" expand:NO]; Node *node4 = [[Node alloc]initWithParentId:@"国家1" nodeId:@"省份3" name:@"江苏" index:@"这个数据是我自己的数据你要不要无所谓" siteSSCID:@"这个数据也是" expand:NO]; Node *node5 = [[Node alloc]initWithParentId:@"省份1" nodeId:@"省份1城市1" name:@"成都" index:@"这个数据是我自己的数据你要不要无所谓" siteSSCID:@"这个数据也是" expand:NO]; Node *node6 = [[Node alloc]initWithParentId:@"省份1" nodeId:@"省份1城市2" name:@"巴中" index:@"这个数据是我自己的数据你要不要无所谓" siteSSCID:@"这个数据也是" expand:NO]; Node *node7 = [[Node alloc]initWithParentId:@"省份1" nodeId:@"省份1城市3" name:@"内江" index:@"这个数据是我自己的数据你要不要无所谓" siteSSCID:@"这个数据也是" expand:NO]; Node *node8 = [[Node alloc]initWithParentId:@"省份2" nodeId:@"省份2城市1" name:@"温州" index:@"这个数据是我自己的数据你要不要无所谓" siteSSCID:@"这个数据也是" expand:NO]; Node *node9 = [[Node alloc]initWithParentId:@"省份3" nodeId:@"省份3城市1" name:@"常州" index:@"这个数据是我自己的数据你要不要无所谓" siteSSCID:@"这个数据也是" expand:NO]; Node *node10 = [[Node alloc]initWithParentId:@"国家2" nodeId:@"国家2省份1" name:@"莫斯科" index:@"这个数据是我自己的数据你要不要无所谓" siteSSCID:@"这个数据也是" expand:NO]; Node *node11 = [[Node alloc]initWithParentId:@"国家2" nodeId:@"国家2省份2" name:@"除了莫斯科,我也不知道有那个城市" index:@"这个数据是我自己的数据你要不要无所谓" siteSSCID:@"这个数据也是" expand:NO]; //造个四级菜单数据 Node *node12 = [[Node alloc]initWithParentId:@"省份1城市1" nodeId:@"省份1城市1区域1" name:@"青羊区" index:@"这个数据是我自己的数据你要不要无所谓" siteSSCID:@"这个数据也是" expand:NO]; Node *node13 = [[Node alloc]initWithParentId:@"省份1城市1" nodeId:@"省份1城市1区域2" name:@"高新区" index:@"这个数据是我自己的数据你要不要无所谓" siteSSCID:@"这个数据也是" expand:NO]; Node *node14 = [[Node alloc]initWithParentId:@"省份1城市1" nodeId:@"省份1城市1区域3" name:@"武侯区" index:@"这个数据是我自己的数据你要不要无所谓" siteSSCID:@"这个数据也是" expand:NO]; NSArray *arry = @[node,node1,node2,node3,node4,node5,node6,node7,node8,node9,node10,node11,node12,node13,node14]; return arry; } #pragma mark *** Lazy Loading *** - (TreeTableView *)treeTableView{ if (!_treeTableView) { _treeTableView = [[TreeTableView alloc]initWithFrame:CGRectMake(0, 20, self.view.bounds.size.width, self.view.bounds.size.height)]; } return _treeTableView; }
相关文章推荐
- javascript与jquery混合而成的动态无限树形结构的菜单
- 别人博客转的 学习EXTJS框架并完成EXTJS+SSH2+JSON实现树形结构(一)
- iOS开发大神的博客
- 自定义类 无极限树形结构菜单(繁杂版)
- JSONArray无极限树形结构菜单(精简版)
- 以前写的两个CSS树形菜单
- iOS组件化思路-大神博客研读和思考
- 优先队列,由于优先队列的树形结构是完全二叉树,所以可以使用数组的形式去存储,我的上一个博客是用二叉树的形式去存储优先队列的
- jQuery+zTree加载树形结构菜单
- iOS大神技术博客
- 多叉树结合JavaScript树形控件实现无限级树形菜单(一种构建多级有序树形结构JSON(或XML)数据源的方法)
- ios-使用树形结构导航模式
- 54、实例 jQuery设计树形结构(类似多级菜单)
- 没事的时候看看大神的博客(iOS界好的博客)
- iOS大神博客(持续更新中。。。)
- 多叉树结合JavaScript树形控件实现无限级树形菜单(一种构建多级有序树形结构JSON(或XML)数据源的方法)
- 超多树形结构的JavaScript菜单实例
- 黑马程序员——Objective-C程序设计(第4版)学习笔记之05-循环结构——黑马 IOS 技术博客
- 利用多叉树实现Ext JS中的无限级树形菜单(一种构建多级有序树形结构JSON的方法)
- jQuery+zTree加载树形结构菜单