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

来源:互联网 发布:国际原油行情分析软件 编辑:程序博客网 时间:2024/06/05 23:07

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


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 *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;}









0 0
原创粉丝点击