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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。