IOS development UI-a highly scalable tree control, iosui
I. Introduction
Tree controls are commonly used in multi-column lists and multi-level menus, such as country-province-city multi-level selection, school-Major-class multi-level selection, and so on. However, the built-in controls in IOS do not have tree controls. To use tree controls in IOS development, you usually need to expand the UITableView list controls by yourself.
Now, we open source a self-written, highly scalable and reusable IOS tree structure control.
Supports an infinitely polar tree structure.
The non-recursive method is used.
The code is easy to understand and easy to expand.
The image is shown as follows:
2. Step 1: create a data model
ParentId: ID of the parent control of the node. If it is-1, the node is the root node.
NodeId: the ID of each node. It is the unique identifier of each node.
Name: node name
Depth: depth in the tree structure of the node. The root node depth is 0.
Expand: whether the node is in the expanded status
/*** Each Node type */@ interface Node: NSObject @ property (nonatomic, assign) int parentId; // id of the parent Node, -1 indicates that the node is the root node @ property (nonatomic, assign) int nodeId; // The id @ property (nonatomic, strong) NSString * name of the current node; // name of the current node @ property (nonatomic, assign) int depth; // 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
Step 2: assemble the data according to the above data model. The following shows the three levels of directories in the country-identity-city.
// ---------------------------------- Provincial/municipal map of China, 3, 2, 1 region 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 * provinw1 = [[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]; // ------------------------------------ U.S. provincial/municipal map 0, 1, 2 province Node * country2 = [[Node alloc] initWithParentId:-1 nodeId: 10 name: @ "us" depth: 0 expand: YES]; Node * province4 = [[Node alloc] initWithParentId: 10 nodeId: 11 name: @ "New York" depth: 1 expand: NO]; Node * province5 = [[Node alloc] initWithParentId: 10 nodeId: 12 name: @ "Dezhou" 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: @ "La" depth: 2 expand: NO]; Node * city9 = [[Node alloc] initWithParentId: 14 nodeId: 16 name: @ "San Francisco" depth: 2 expand: NO]; // ---------------------------------- Japan's provincial/municipal map 0, 1, 2 worker Node * country3 = [[Node alloc] initWithParentId:-1 nodeId: 17 name: @ "Japan" depth: 0 expand: YES]; NSArray * data = [NSArray counters: country1, example, city1, city2, city3, province2, city4, city5, provine3, city6, country2, province4, example, city7, province6, city8, city9, country3, nil];
Step 3: Use the preceding data to initialize the TeeTableView.
TreeTableView *tableview = [[TreeTableView alloc] initWithFrame:CGRectMake(0, 20, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)-20) withData:data];[self.view addSubview:tableview];
With the preceding three steps, you can integrate the tree control into your project.
Iii. Implementation Principle
The list of tree structures uses the UITableView control. However, how to enable the UItableView to dynamically increase or delete the cell of the specified number of rows is the key to implementing the tree structure.
At this time, we need to use two rows of UItableView:
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
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. Both functions have multiple animation effects, it makes the process of deletion and insertion not too abrupt, with a gradient, and has a good user experience.
I tried these animations:
UITableViewRowAnimationFade: gradient effect
UITableViewRowAnimationRight: enter on the right and disappear on the right
UITableViewRowAnimationLeft: enter on the left and disappear on the left
UITableViewRowAnimationTop: Top entry, top disappears
UITableViewRowAnimationBottom: enter at the top and disappear at the bottom
Note:
When calling insertRowsAtIndexPaths and deleteRowsAtIndexPaths, you must first change the data source and call the preceding function. Otherwise, a crash is generated.
Next, we will display the main code of the TreeTableView, because the amount of code is not large, and the comments in the Code are also comprehensive, so we hope to help you understand it.
# Import "TreeTableView. h "# import" Node. h "@ interface TreeTableView () <UITableViewDataSource, UITableViewDelegate> @ property (nonatomic, strong) NSArray * data; // transmit the organized data (full data) @ property (nonatomic, strong) NSMutableArray * tempData; // used to store data sources (partial data) @ end @ implementation TreeTableView-(instancetype) initWithFrame :( CGRect) frame withData: (NSArray *) data {self = [super initWithFrame: frame style: UITableVie WStyleGrouped]; if (self) {self. dataSource = self; self. delegate = self; _ data = data; _ tempData = [self createTempData: data];} return self;}/*** initialize 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 tempAr Ray ;}# 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 progress: N ODE_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 :( UI TableView *) tableView heightForHeaderInSection :( NSInteger) section {return 0.01;}-(CGFloat) tableView :( UITableView *) tableView heightForRowAtIndexPath :( NSIndexPath *) indexPath {return 40;}-(CGFloat) tableView :( UITableView *) tableView example :( NSInteger) section {return 0.01;} # pragma mark-UITableViewDelegate # pragma mark-Optional-(void) tableView :( UITableView *) tableView didSelect RowAtIndexPath :( NSIndexPath *) indexPath {// modify the data source 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 ++ }}// obtain the indexPath NSMutableArray * indexPathArray = [NSMutableArray array]; for (NSUInteger I = startPosition; I <endPosition; I ++) to be corrected) {NSIndexPath * tempIndexPath = [NSIndexPath indexPathForRow: I inSection: 0]; [indexPathArray addObject: tempIndexPath];} // insert or delete a node if (expand) {[self insertRowsAtIndexPaths: indexPathArray withRowAnimation: parent];} else {[self deleteRowsAtIndexPaths: indexPathArray withRowAnimation: parent];}/*** delete all child nodes (including grandson nodes) under the parent node) ** @ param parentNode parent node ** @ return refers to the length of the parent node from the position of the parent node, that is, the number of all child nodes under the parent Node */-(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 ;}
Iv. Summary
In the demo project, I use the cell that comes with the system for each cell. The style is relatively simple. If you want to display more beautiful Styles, you can customize the cell.
At the same time, you can also extend the data model to more complex business processing. For example:
V,
Github: https://github.com/yixiangboy/TreeTableView
If you think it is useful to you, give it a star. Your support is my motivation.
5. The words of the blogger
I have read many other people's blogs and learned a lot. Now, I want to thank my blog and hope to help you. If your work is not very busy, write an article every week.
My contact information: Sina Weibo
Copyright Disclaimer: This article is an original article by the blogger and cannot be reproduced without the permission of the blogger.