Realization renderings
Without further ado, let’s get straight to the code;
Create an entity class to store row data
MenuNode.h
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface MenuNode: NSObject @property (nonatomic, copy) NSString * parentId;//The id of the parent node, if it is -1, it means that the node is the root node @property (nonatomic, copy) NSString * nodeId;//The id of this node @property (nonatomic, copy) NSString *name;//The name of this node @property (nonatomic, copy) NSString *code;//Other parameters @property (nonatomic, copy) NSString *other;//Other parameters @property (nonatomic, assign) int depth;//The depth of the node @property (nonatomic, assign) int sortNo;//The sequence number of the node @property (nonatomic, assign) BOOL isParent;//Whether this node has child nodes @property (nonatomic, assign) BOOL childIsExpand;//Whether the child nodes of this node are expanded @property (nonatomic, assign) BOOL expand;//Whether the node is in the expanded state @end NS_ASSUME_NONNULL_END
MenuNode.m
#import "MenuNode.h" @implementationMenuNode @end
Picture resources
TreeMenuView.h
#import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN @class MenuNode; @protocol TreeMenuCellDelegate <NSObject> -(void)cellClick : (MenuNode *)node; -(void)chooseDataChange:(NSDictionary *)chooseData; @end @interface TreeMenuView : UITableView @property (nonatomic, weak) id<TreeMenuCellDelegate> treeMenuCellDelegate; @property (nonatomic, strong) NSMutableDictionary *chooseData;//Used to store selected data; @property (nonatomic,assign) BOOL *isRadio; // Whether to select multiple times; typedef void (^treeMenuBlock)(NSMutableDictionary *); @property (nonatomic,copy) treeMenuBlock block; -(instancetype)init; -(instancetype)initWithFrame:(CGRect)frame Data:(NSArray *)data ChooseData:(NSMutableDictionary *)chooseData; -(void)reloadData : (NSArray *)data ChooseData:(NSMutableDictionary *) chooseData; -(void)reloadData : (NSArray *)data; -(void)clearRadio; -(void)unExpandAll; @end NS_ASSUME_NONNULL_END
TreeMenuView.m
#import "TreeMenuView.h" #import "MenuNode.h" @interface TreeMenuView ()<UITableViewDataSource,UITableViewDelegate> @property (nonatomic, strong) NSArray *data;//The organized data is passed in (full data) @property (nonatomic, strong) NSMutableArray *tempData;//Used to store data source (partial data) @end @implementation TreeMenuView{<!-- --> MenuNode *node; MenuNode *parentNode; } -(instancetype)init{<!-- --> self=[super init]; if (self) {<!-- --> self.dataSource = self; self.delegate = self; _data= [NSMutableDictionary dictionary]; _tempData = [self createTempData:_data]; _chooseData= [NSMutableDictionary dictionary]; _isRadio=YES;//Default radio selection; } return self; } -(instancetype)initWithFrame:(CGRect)frame Data:(NSArray *)data ChooseData:(NSMutableDictionary *)chooseData{<!-- --> self = [super initWithFrame:frame style:UITableViewStyleGrouped]; if (self) {<!-- --> self.dataSource = self; self.delegate = self; if(data) _data = data; else _data= [NSMutableDictionary dictionary]; _tempData = [self createTempData:data]; if(chooseData) _chooseData=chooseData; else _chooseData= [NSMutableDictionary dictionary]; _isRadio=YES;//Default radio selection; } return self; } /** * Initialize data source * The default expansion is that the display/level is 1 */ -(NSMutableArray *)createTempData : (NSArray *)data{<!-- --> NSMutableArray *tempArray = [NSMutableArray array]; for (int i=0; i<data.count; i + + ) {<!-- --> MenuNode *node = [_data objectAtIndex:i]; if (node.expand) {<!-- --> [tempArray addObject:node]; } } if(tempArray.count==0){<!-- --> for (int i=0; i<data.count; i + + ) {<!-- --> MenuNode *node = [_data objectAtIndex:i]; if (node.depth==1) {<!-- --> [tempArray addObject:node]; } } } return tempArray; } -(void)reloadData : (NSArray *)data{<!-- --> _data = data; _tempData = [self createTempData:data]; [self reloadData]; } -(void)reloadData : (NSArray *)data ChooseData:(NSMutableDictionary *) chooseData{<!-- --> _data = data; if(chooseData) _chooseData=chooseData; else _chooseData= [NSMutableDictionary dictionary]; _tempData = [self createTempData:data]; [self reloadData]; } -(void)clearRadio{<!-- --> _chooseData= [NSMutableDictionary dictionary]; [self reloadData]; } -(void)unExpandAll{<!-- --> for (int i=0; i<_data.count; i + + ) {<!-- --> node = [_data objectAtIndex:i]; node.expand = NO; } } #pragma mark - UITableViewDataSource #pragma mark-Required -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{<!-- --> return _tempData.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{<!-- --> static NSString *NODE_CELL_ID = @"node_cell_id"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NODE_CELL_ID]; node = [_tempData objectAtIndex:indexPath.row]; if(!cell){<!-- --> cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:NODE_CELL_ID]; } for (UIView *view in [cell.contentView subviews]) {<!-- --> [view removeFromSuperview]; } if(node.isParent){<!-- --> UIImageView *img=[[UIImageView alloc] initWithFrame:CGRectMake(15*node.depth,12,16,16)]; if(node.expand) {<!-- --> [img setImage:[UIImage imageNamed:@"caret-down.png"]]; } else {<!-- --> [img setImage:[UIImage imageNamed:@"caret-right.png"]]; } [cell.contentView addSubview:img]; } UIButton *CheckButton=[[UIButton alloc] initWithFrame:CGRectMake(15*node.depth + 20,10,20,20)]; [CheckButton setTitleColor:[UIColor colorWithRed:7.0f/255.0f green:50.0f/255.0f blue:80.0f/255.0f alpha:1.0f] forState:UIControlStateNormal]; [CheckButton setTitleColor:[UIColor colorWithRed:210.0f/255.0f green:210.0f/255.0f blue:210.0f/255.0f alpha:1.0f] forState:UIControlStateHighlighted]; [CheckButton setTitleColor:[UIColor colorWithRed:210.0f/255.0f green:210.0f/255.0f blue:210.0f/255.0f alpha:1.0f] forState:UIControlStateSelected]; if(_isRadio){<!-- --> [CheckButton setImage:[UIImage imageNamed:@"radiobox_unselected.png"] forState:UIControlStateNormal]; [CheckButton setImage:[UIImage imageNamed:@"radiobox_selected.png"] forState:UIControlStateSelected]; }else{<!-- --> [CheckButton setImage:[UIImage imageNamed:@"checkbox_unselected.png"] forState:UIControlStateNormal]; [CheckButton setImage:[UIImage imageNamed:@"checkbox_selected.png"] forState:UIControlStateSelected]; } NSString *nodeid=node.nodeId; if([_chooseData objectForKey:nodeid]) CheckButton.selected=YES; else CheckButton.selected=NO; CheckButton.tag=indexPath.row; [CheckButton addTarget:self action:@selector(didSelected:) forControlEvents:UIControlEventTouchUpInside]; [cell.contentView addSubview:CheckButton]; UILabel *title=[[UILabel alloc] initWithFrame:CGRectMake(15*node.depth + 20 + 25,0,self.frame.size.width-(15*node.depth + 20 + 25),40)]; title.numberOfLines=0; title.lineBreakMode=UILineBreakModeWordWrap; title.text=node.name; title.font=[UIFont systemFontOfSize:12.0f]; [cell.contentView addSubview:title]; UIControl *actionView=[[UIControl alloc] initWithFrame:CGRectMake( 15*node.depth + 20 + 25,0,self.frame.size.width-(15*node.depth + 20 + 25),40)]; actionView.backgroundColor=[UIColor redColor]; actionView.tag = indexPath.row; if([_chooseData objectForKey:nodeid]) actionView.selected=YES; else actionView.selected=NO; actionView.backgroundColor = [UIColor clearColor]; [actionView addTarget:self action:@selector(selectAction:) forControlEvents:UIControlEventTouchUpInside]; NSDictionary *attrs = @{<!-- -->NSFontAttributeName:[UIFont systemFontOfSize:12.0f]}; CGSize size=[node.name sizeWithAttributes:attrs]; if(self.frame.size.width>=15*node.depth + 20 + 25 + size.width){<!-- --> actionView.frame = CGRectMake(15*node.depth + 20 + 25, 0, 20 + size.width, 40); }else{<!-- --> actionView.frame = CGRectMake(15*node.depth + 20 + 25, 0, self.frame.size.width-(15*node.depth + 20 + 25), 40); } [cell.contentView addSubview:actionView]; return cell; } -(void)selectAction:(UIControl *)view{<!-- --> view.selected=!view.selected; int row=view.tag; MenuNode *node=[_tempData objectAtIndex:row]; if (view.selected) {<!-- --> if(_isRadio) [_chooseData removeAllObjects]; [_chooseData setObject:node forKey:node.nodeId]; }else{<!-- --> [_chooseData removeObjectForKey:node.nodeId]; } if(!_isRadio)//If it is not a single selection, select its child; for (int i=0; i<_data.count; i + + ) {<!-- --> MenuNode *cnode = [_data objectAtIndex:i]; if (cnode.parentId!=[NSNull null] & amp; & amp;[cnode.parentId isEqualToString: node.nodeId ]) {<!-- --> if(view.selected) [_chooseData setObject:cnode forKey:cnode.nodeId]; else{<!-- --> [_chooseData removeObjectForKey:cnode.nodeId]; } } } if (_treeMenuCellDelegate & amp; & amp; [_treeMenuCellDelegate respondsToSelector:@selector(chooseDataChange:)]) {<!-- --> [_treeMenuCellDelegate chooseDataChange:_chooseData]; } self.block(_chooseData); [self reloadData]; } -(void)didSelected:(UIButton *)btn{<!-- --> btn.selected=!btn.selected; int row=btn.tag; MenuNode *node=[_tempData objectAtIndex:row]; if (btn.selected) {<!-- --> if(_isRadio) [_chooseData removeAllObjects]; [_chooseData setObject:node forKey:node.nodeId]; }else{<!-- --> [_chooseData removeObjectForKey:node.nodeId]; } if(!_isRadio)//If it is not a single selection, select its child; for (int i=0; i<_data.count; i + + ) {<!-- --> MenuNode *cnode = [_data objectAtIndex:i]; if (cnode.parentId!=[NSNull null] & amp; & amp;[cnode.parentId isEqualToString: node.nodeId ]) {<!-- --> if(btn.selected) [_chooseData setObject:cnode forKey:cnode.nodeId]; else{<!-- --> [_chooseData removeObjectForKey:cnode.nodeId]; } } } if (_treeMenuCellDelegate & amp; & amp; [_treeMenuCellDelegate respondsToSelector:@selector(chooseDataChange:)]) {<!-- --> [_treeMenuCellDelegate chooseDataChange:_chooseData]; } self.block(_chooseData); [self reloadData]; } #pragma mark - Optional - (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; } #pragma mark-UITableViewDelegate #pragma mark - Optional - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{<!-- --> // NSLog(@"SelectRow:%@",_data); //Modify the data source first parentNode = [_tempData objectAtIndex:indexPath.row]; if(parentNode.isParent){<!-- --> parentNode.expand=!parentNode.expand; NSUInteger startPosition = indexPath.row + 1; NSUInteger endPosition = startPosition; BOOL expand = NO; for (int i=0; i<_data.count; i + + ) {<!-- --> node = [_data objectAtIndex:i]; if (node.parentId!=[NSNull null] & amp; & amp;[node.parentId isEqualToString: parentNode.nodeId]) {<!-- --> // node.expand = !node.expand; node.depth = parentNode.depth + 1; if (parentNode.expand) {<!-- --> [_tempData insertObject:node atIndex:endPosition]; expand = YES; endPosition + + ; }else{<!-- --> expand = NO; endPosition = [self removeAllNodesAtParentNode:parentNode]; break; } } } // NSLog(@"SelectRow:%@",_tempData); //Get the indexPath that needs to be corrected NSMutableArray *indexPathArray = [NSMutableArray array]; for (NSUInteger i=startPosition; i<endPosition; i + + ) {<!-- --> NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:0]; [indexPathArray addObject:tempIndexPath]; } //Insert or delete related nodes if (expand) {<!-- --> [self insertRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationNone]; }else{<!-- --> [self deleteRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationNone]; } }else{<!-- --> if (_treeMenuCellDelegate & amp; & amp; [_treeMenuCellDelegate respondsToSelector:@selector(cellClick:)]) {<!-- --> [_treeMenuCellDelegate cellClick:parentNode]; } } [self reloadData]; } /** * Delete all child nodes under the parent node (including grandchild nodes) * * @param parentNode parent node * * @return The position of the next adjacent node of the same level under the parent node */ -(NSUInteger)removeAllNodesAtParentNode : (MenuNode *)parentNode{<!-- --> NSUInteger startPosition = [_tempData indexOfObject:parentNode]; NSUInteger endPosition = startPosition; for (NSUInteger i=startPosition + 1; i<_tempData.count; i + + ) {<!-- --> node = [_tempData objectAtIndex:i]; endPosition + + ; if (node.depth <= parentNode.depth) {<!-- --> break; } if(endPosition == _tempData.count-1){<!-- --> endPosition + + ; node.expand = NO; break; } node.expand = NO; } if (endPosition>startPosition) {<!-- --> [_tempData removeObjectsInRange:NSMakeRange(startPosition + 1, endPosition-startPosition-1)]; } return endPosition; } @end
use
TreeMenuView *areaMenu = [[TreeMenuView alloc] init]; areaMenu.layer.cornerRadius=8; areaMenu.estimatedRowHeight = 0; areaMenu.estimatedSectionHeaderHeight = 0; areaMenu.estimatedSectionFooterHeight = 0; areaMenu.isRadio=YES; areaMenu.block = ^(NSMutableDictionary *chooseData) {<!-- -->//Select callback };
data binding
MenuNode *ntmp = [[MenuNode alloc] init]; [areaListData addObject:ntmp]; [areaMenu reloadData:areaListData ChooseData:areaChooseData];