您的位置:首页 > 移动开发 > IOS开发

iOS 树形结构菜单(参照以前大神写的博客)

2016-09-24 13:01 429 查看
说明:写的东西是参照某位大神写的博客,但是我自己的数据他有些没有,然后我就根据大神的博客重新写了下,希望对大家有用

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

@end
ViewController.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;
}


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: