A list,

Tree controls are commonly used in multi-column lists and multi-level menus, such as country-provine-city multi-level selection, school-professional-class multi-level selection, and so on. However, there are no tree controls in IOS. To use tree controls in IOS development, we usually need to extend the UITableView list control ourselves. Now open source here to write a high scalability, high reuse of the IOS tree structure control. Support infinite pole tree structure. It’s non-recursive. The code is easy to understand and easy to expand. The picture is shown below:


image

2. Instructions for use

Step 1: Build the data model

If the value is -1, the node is the root node. NodeId: indicates the id of each node, which uniquely identifies each node. Name: Indicates the node name. Depth in the tree structure of the node. The depth of the root node is 0 expand: Indicates whether the node is expanding

/** * each node type */
@interface Node : NSObject

@property (nonatomic , assign) int parentId;// Id of the parent node. The value -1 indicates that the parent node is the root node

@property (nonatomic , assign) int nodeId;// Id of this node

@property (nonatomic , strong) NSString *name;// The name of this node

@property (nonatomic , assign) int depth;// The depth of the node

@property (nonatomic , assign) BOOL expand;// Whether the node is in the expanded state

/** * Quickly instantiate the object model */
- (instancetype)initWithParentId : (int)parentId nodeId : (int)nodeId name : (NSString *)name depth : (int)depth expand : (BOOL)expand;

@end
Copy the code

Step 2: Assemble the data according to the above data model. The following is a three-level catalog of country-identity-city for demonstration.

/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- China's provincial cities diagram 3, 2, 1 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Node *country1 = [[Node alloc] initWithParentId:- 1 nodeId:0 name:@ "China" depth:0 expand:YES];
Node *province1 = [[Node alloc] initWithParentId:0 nodeId:1 name:@ "jiangsu" depth:1 expand:NO];
Node *city1 = [[Node alloc] initWithParentId:1 nodeId:2 name:Nantong "@" depth:2 expand:NO];
Node *city2 = [[Node alloc] initWithParentId:1 nodeId:3 name:@ "nanjing" depth:2 expand:NO];
Node *city3 = [[Node alloc] initWithParentId:1 nodeId:4 name:@ "suzhou" depth:2 expand:NO];
Node *province2 = [[Node alloc] initWithParentId:0 nodeId:5 name:@ "guangdong" depth:1 expand:NO];
Node *city4 = [[Node alloc] initWithParentId:5 nodeId:6 name:Shenzhen "@" depth:2 expand:NO];
Node *city5 = [[Node alloc] initWithParentId:5 nodeId:7 name:@ "guangzhou" depth:2 expand:NO];
Node *province3 = [[Node alloc] initWithParentId:0 nodeId:8 name:@ "zhejiang" depth:1 expand:NO];
Node *city6 = [[Node alloc] initWithParentId:8 nodeId:9 name:@ "hangzhou" depth:2 expand:NO];
/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the United States the provincial cities of diagram 0 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Node *country2 = [[Node alloc] initWithParentId:- 1 nodeId:10 name:@ "American" depth:0 expand:YES];
Node *province4 = [[Node alloc] initWithParentId:10 nodeId:11 name:@New York State depth:1 expand:NO];
Node *province5 = [[Node alloc] initWithParentId:10 nodeId:12 name:@ "Texas" depth:1 expand:NO];
Node *city7 = [[Node alloc] initWithParentId:12 nodeId:13 name:@" Houston" depth:2 expand:NO];
Node *province6 = [[Node alloc] initWithParentId:10 nodeId:14 name:@ "California" depth:1 expand:NO];
Node *city8 = [[Node alloc] initWithParentId:14 nodeId:15 name:@los Angeles depth:2 expand:NO];
Node *city9 = [[Node alloc] initWithParentId:14 nodeId:16 name:@San Francisco depth:2 expand:NO];

/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- Japan's provincial cities diagram 0 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Node *country3 = [[Node alloc] initWithParentId:- 1 nodeId:17 name:@ "Japan" depth:0 expand:YES];
NSArray *data = [NSArrayarrayWithObjects:country1,province1,city1,city2,city3,province2,city4,city5,province3,city6,country2,province4,province5 ,city7,province6,city8,city9,country3,nil];
Copy the code

Step 3: Use the above data to initialize the TeeTableView.

1.TreeTableView *tableview = [[TreeTableView alloc] initWithFrame:CGRectMake(0.20.CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)- 20) withData:data];
[self.view addSubview:tableview];
Copy the code

By following these three simple steps, you can integrate the tree control into your project.

Three, the implementation principle

The tree structure of the list is actually used by the UITableView control, but how to make the UITableView can dynamically add and remove the specified number of cells is the key to achieve the tree structure. In this case, we need to use two rows from the UItableView:

- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
Copy the code

The first function is used to insert cells at the specified position, and the second function is used to delete cells at the specified position, and both functions have a variety of animation effects to make the deletion and insertion process less abrupt and gradual, which is a good user experience. For this a few animation did try: UITableViewRowAnimationFade: gradient effect UITableViewRowAnimationRight: the right to enter, on the right side disappear UITableViewRowAnimationLeft: The top on the left, the left side disappear UITableViewRowAnimationTop:, top disappear UITableViewRowAnimationBottom: the top and bottom

Note:

InsertRowsAtIndexPaths and deleteRowsAtIndexPaths must change the data source first, otherwise crash will occur.

Now I’m going to show you the main code of TreeTableView, because the code is not much, and the code is full of comments, hopefully to help you understand.

#import "TreeTableView.h"
#import "Node.h"

@interface TreeTableView()"UITableViewDataSource.UITableViewDelegate>

@property (nonatomic , strong) NSArray *data;// Pass organized data (full data)

@property (nonatomic , strong) NSMutableArray *tempData;// Used to store data source (partial data)


@end

@implementation TreeTableView- (instancetype)initWithFrame:(CGRect)frame withData : (NSArray *)data{
    self = [super initWithFrame:frame style:UITableViewStyleGrouped];
    if (self) {
        self.dataSource = self;
        self.delegate = self;
        _data = data;
        _tempData = [self createTempData:data];
    }
    return self;
}

/** * Initializes the data source */- (NSMutableArray *)createTempData : (NSArray *)data{
    NSMutableArray *tempArray = [NSMutableArray array];
    for (int i=0; i<data.count; i++) {
        Node *node = [_data objectAtIndex:i];
        if(node.expand) { [tempArray addObject:node]; }}return tempArray;
}


#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];
    if(! cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:NODE_CELL_ID];
    }

    Node *node = [_tempData objectAtIndex:indexPath.row];

    NSMutableString *name = [NSMutableString string];
    for (int i=0; i<node.depth; i++) {
        [name appendString:@ ""];
    }
    [name appendString:node.name];

    cell.textLabel.text = name;

    return cell;
}


#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{
    // Modify the data source first
    Node *parentNode = [_tempData objectAtIndex:indexPath.row];
    NSUInteger startPosition = indexPath.row+1;
    NSUInteger endPosition = startPosition;
    BOOL expand = NO;
    for (int i=0; i<_data.count; i++) {
        Node *node = [_data objectAtIndex:i];
        if(node.parentId == parentNode.nodeId) { node.expand = ! node.expand;if (node.expand) {
                [_tempData insertObject:node atIndex:endPosition];
                expand = YES;
            }else{
                expand = NO;
                endPosition = [self removeAllNodesAtParentNode:parentNode];
                break; } endPosition++; }}// 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]; }}** @param parentNode parentNode ** @return the distance between the adjacent parentNode and the parentNode, that is, the number of children under the parentNode */- (NSUInteger)removeAllNodesAtParentNode : (Node *)parentNode{
    NSUInteger startPosition = [_tempData indexOfObject:parentNode];
    NSUInteger endPosition = startPosition;
    for (NSUInteger i=startPosition+1; i<_tempData.count; i++) {
        Node *node = [_tempData objectAtIndex:i];
        endPosition++;
        if (node.depth == parentNode.depth) {
            break;
        }
        node.expand = NO;
    }
    if (endPosition>startPosition) {
        [_tempData removeObjectsInRange:NSMakeRange(startPosition+1, endPosition-startPosition- 1)];
    }
    return endPosition;
}
Copy the code

Four,

In the demonstration project, I used the system’s own cell for each cell, and the style is relatively simple. If you want to show a more beautiful style, you can customize the cell. At the same time, you can extend the data model to more complex business processes. For example, the following scenario:


2.gif

Five, download address

Demo download address: this is a my iOS communication group: 624212887, group file download, no matter you are small white or big ox warmly welcome to enter the group, share interview experience, discuss technology, we exchange learning to grow together! Hope to help developers avoid detours.

If you think you still have some use, pay attention to xiaobian + like this article. Your support is my motivation to continue.

Next article: iOS development UI – a support for text and text mixed ActionSheet

Article from the network, if there is infringement, please contact xiaobian to delete.