美文网首页UIKitios相关文章ios 知识点
iOS 动态树形结构 - 实现多级菜单,附带复选框功能

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

作者: 桐雨知秋 | 来源:发表于2017-12-01 16:07 被阅读157次

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

    目标:

    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

    相关文章

      网友评论

      • 悟空_大师兄_:大神 你好 你这个是什么判断选中的是哪一些 没看到 选中的有储存起来吗 没看到
        悟空_大师兄_:@桐雨知秋 点击复选框 对应的数据 添加到数组
        桐雨知秋:@空白_8a44 你是想拿到选中的数据?
      • lym不解释:你好,请教一下,如果你的所有子集的数组key = second_category,那你处理数据传入key的方法要怎么处理
        lym不解释:我尝试吧你的本地数据数组key全部改成一样的,这个时候初始化数据的时候调用[dataHandler setLevelKeys:@[@"second_category", @"second_category", @"second_category"]]; 效果就是有多少个层级

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

      本文链接:https://www.haomeiwen.com/subject/oejwbxtx.html