iOS 动态树形结构 - 实现多级菜单,附带复选框功能

关键词:递归 多级菜单 复选

目标:

1.显示多级菜单,默认显示一级.
2.可以通过点击有子级的行展开菜单
3.通过复选框,改变选中状态。状态有全选、半选、未选中
4.可以扩展获取当前所选的条目集合

menu.gif

实现过程:

数据处理

1.首先根Datasource进行数据处理
2.生成一个handler:MultilevelDataHandler 将数据处理逻辑在handle处理,将数据处理隔离

MultilevelDataHandler *dataHandler = [MultilevelDataHandler sharedHandler];
[dataHandler setLevelKeys:@[@"second_category", @"knowledge"]]; //由于源数据中每层数据可能采用不同的key,所有我把每层的key依次添加到数组里面,以便数据转化
[dataHandler setReDataSource:dataSource]; // 将源数据交给handle处理

3.建立一个数据模型,需要用一些属性记录层级关系。最后我用了一个字典来记录原始的数据信息。

//
// MultilevelMenuModel.h
// MultilevelMenuWithCheckbox
//// Created by hyt on 2017/10/31.
//  Copyright © 2017年 hyt. All rights reserved.
//#importtypedef NS_ENUM (NSInteger, MMSelectState){

selectNone,

selectHalf,

selectAll

};

@interface MultilevelMenuModel : NSObject

@property (nonatomic, assign) NSInteger MMLevel;

@property (nonatomic, assign) NSInteger MMIndex;

//@property (nonatomic, assign) NSInteger MMSuperIndex;

@property (nonatomic, strong) NSMutableArray *locationArray;

@property (nonatomic, strong) NSArray *MMSubArray;

@property (nonatomic, assign) MMSelectState MMSelectState;

@property (nonatomic, assign) BOOL MMIsOpen;

@property (nonatomic, strong) NSDictionary *dataDict; // original data

@end

这里是Demo的数据Json


json数据:

[{

"id": "",

"name": "",

"type": "",

"second_category": [{

"id": "",

"name": "",

"type": "",

"knowledge": [{

"id": "",

"name": "",

"type": ""

}]

}]

}]

4.将jsonDictionary转化成数据模型的时候,把层级关系也一并赋值。

由于数据层级数量的不确定性,这里用递归的方式把每层的数据结构都放到其父类的subArray当中。


- (MultilevelMenuModel *)getModelByDict:(NSDictionary *)dict

modelLevel:(NSInteger)level

modelIndex:(NSInteger)index

superIndex:(NSInteger)superIndex

locationArray:(NSMutableArray *)loactionArray{

MultilevelMenuModel *levelModel = [[MultilevelMenuModel alloc] init];

levelModel.MMLevel = level;

levelModel.MMIndex = index++;

//    levelModel.MMSuperIndex = superIndex;

levelModel.dataDict = [NSDictionary dictionaryWithDictionary:dict];

levelModel.locationArray = [NSMutableArray arrayWithArray:loactionArray];

[levelModel.locationArray addObject:[NSNumber numberWithInteger:superIndex]];

if ([self checkModelHasSubArray:levelModel]) {

[self setModelSubArray:levelModel];

}

return levelModel;

}

- (void)setModelSubArray:(MultilevelMenuModel *)model {

NSString *key = [self getSubKeyByModel:model];

NSArray *subDictArray = model.dataDict[key];

model.MMSubArray = [self getModelArrayFromDictArray:subDictArray

modelLevel:model.MMLevel + 1

superIndex:model.MMIndex

locationArray:model.locationArray];

}

- (NSArray *)getModelArrayFromDictArray:(NSArray *)dictArray

modelLevel:(NSInteger)level

superIndex:(NSInteger)superIndex

locationArray:(NSMutableArray *)locationArray{

NSMutableArray *deArray = [NSMutableArray array];

NSInteger index = 0;

for (NSDictionary *dict in dictArray) {

MultilevelMenuModel *levelModel = [self getModelByDict:dict

modelLevel:level

modelIndex:index++

superIndex:superIndex

locationArray:locationArray];

[deArray addObject:levelModel];

}

return [NSArray arrayWithArray:deArray];

}

5.建一个新的数组用来存储要在tableView上展示的数据模型,按照父类子类,父类子类的顺序排列。我这里默认是把第一级全部关闭展示的

6.实现菜单展开关闭功能

根据点击的model的isOpen属性来判断是否展开。
展开时是把subArray中的子级添加到showData当中去,需注意判断子级当前的状态是否是已展开的,如果是的话需要递归调用展开方法。
关闭时是把subArray中的子级从showData中删除,也用递归的方法把子类的子类也一并删除。
删除子级的时候不影响子级的展开状态。


- (void)addSubModelToShowByModel:(MultilevelMenuModel *)superModel  {

NSInteger superIndex  = [self.showData indexOfObject:superModel]; //

for (MultilevelMenuModel *subModel in [[superModel.MMSubArray reverseObjectEnumerator] allObjects]) {

if (![self.showData containsObject:subModel]) {

[self.showData insertObject:subModel atIndex:superIndex + 1];

}

if (subModel.MMIsOpen == YES && subModel.MMSubArray.count > 0 ) {

[self addSubModelToShowByModel:subModel];

}

}

superModel.MMIsOpen = YES;

}

- (void)removeSubModelFromShowByModel:(MultilevelMenuModel *)superModel

closeIt:(BOOL)close{

if (superModel.MMSubArray.count == 0) {

return;

}

for (MultilevelMenuModel *subModel in superModel.MMSubArray) {

[self removeSubModelFromShowByModel:subModel closeIt:!subModel.MMIsOpen];

[self.showData removeObject:subModel];

}

superModel.MMIsOpen = !close;

}

7.实现复选框功能

用三个方法分别实现复修改选框的三种状态
每个方法中,都需要考虑当前model的状态改变对其父级与子级的影响
用model的subArray找到其子级,用locationArray记录的坐标找到其父类
父级和子级的状态改变也会影响到他们的父级和子级,所有用递归的方式修改状态


- (void)setModelToBeStateNone:(MultilevelMenuModel *)model {

// make current model unselected

model.MMSelectState = selectNone;

// make sublevel unselected

if (model.MMSubArray.count > 0) {

for (MultilevelMenuModel *subModel in model.MMSubArray) {

if (subModel.MMSelectState != selectNone) {

[self setModelToBeStateNone:subModel];

}

}

}

//make super model unselected

MultilevelMenuModel *supModel = [self findSuperModelBySubModel:model];

if (supModel == nil) {

return;

}

if ([self checkSubModeHasStateAll:supModel]) {

[self setModelToBeStateHalf:supModel];

} else if ([self checkSubModelHasSelected:supModel])  {

[self setModelToBeStateHalf:supModel];

} else {

supModel.MMSelectState = selectNone;

}

}

- (void)setModelToBeStateHalf:(MultilevelMenuModel *)model {

// make current model selected

model.MMSelectState = selectHalf;

//make super model half-selected

MultilevelMenuModel *supModel = [self findSuperModelBySubModel:model];

if (supModel == nil) {

return;

}

[self setModelToBeStateHalf:supModel];

}

- (void)setModelToBeStateAll:(MultilevelMenuModel *)model {

// make current model selected

model.MMSelectState = selectAll;

// make  sublevel selected

if (model.MMSubArray.count > 0) {

for (MultilevelMenuModel *subModel in model.MMSubArray) {

if (subModel.MMSelectState != selectAll) {

[self setModelToBeStateAll:subModel];

}

}

}

//make super model selected

MultilevelMenuModel *supModel = [self findSuperModelBySubModel:model];

if (supModel == nil) {

return;

}

if ([self checkSubModelsBeStateAll:supModel]) {

[self setModelToBeStateAll:supModel];

} else {

[self setModelToBeStateHalf:supModel];

}

}

根据locationArray里记录的每一层父级的序号,找到当前model的父级


- (MultilevelMenuModel *)findSuperModelBySubModel:(MultilevelMenuModel *)subModel {

NSArray *location = subModel.locationArray;

if (location.count == 0) {

return nil;

}

MultilevelMenuModel *resultModel;

NSArray *dataSource = self.modelArray;

int i = 1;

while (i < location.count) {

int index = [location[i] intValue];

resultModel = dataSource[index];

dataSource = resultModel.MMSubArray;

i++;

}

return resultModel;

}

小结

由于层级数量的不确定性,所以多次使用到了递归的方式。要注意递归的结束条件,必须陷入死循环当中。

附上本文的Demo

https://github.com/YuTongHon/MultilevelMenuWithCheckbox

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350