iOS開發UI篇--一個可擴展性極強的樹形控件

1、簡介

樹形控件在多列列表、多級菜單中使用比較常見,好比:國家-省份-城市 多級選擇、學校-專業-班級 多級選擇等等。然而IOS自帶控件中並不存在樹形控件,咱們要在IOS開發中使用樹形控件,一般須要本身擴展UITableView列表控件。
如今在這裏開源一個本身寫的高擴展性,高複用性的IOS樹形結構控件。
支持無限極樹形結構。
使用的是非遞歸方式。
代碼簡單易懂,擴展方便。
圖片演示以下:node

 

 

2、使用說明

第一步:創建數據模型

parentId : 該節點的父控件id號,若是爲-1則表示該節點爲根節點
nodeId : 每一個節點自身的id號,是每一個節點的惟一標示
name : 節點的名稱
depth : 該節點所帶的樹形結構中的深度,根節點的深度爲0
expand : 該節點是否處於展開狀態面試

 1 /**
 2 *  每一個節點類型
 3 */
 4 @interface Node : NSObject
 5 
 6 @property (nonatomic , assign) int parentId;//父節點的id,若是爲-1表示該節點爲根節點
 7 
 8 @property (nonatomic , assign) int nodeId;//本節點的id
 9 
10 @property (nonatomic , strong) NSString *name;//本節點的名稱
11 
12 @property (nonatomic , assign) int depth;//該節點的深度
13 
14 @property (nonatomic , assign) BOOL expand;//該節點是否處於展開狀態
15 
16 /**
17 *快速實例化該對象模型
18 */
19 - (instancetype)initWithParentId : (int)parentId nodeId : (int)nodeId name : (NSString *)name depth : (int)depth expand : (BOOL)expand;
20 
21 @end

 

第二步:按照以上的數據模型,組裝數據,下面以 國家-身份-城市 的三級目錄進行演示。

 1 //----------------------------------中國的省地市關係圖3,2,1--------------------------------------------
 2 Node *country1 = [[Node alloc] initWithParentId:-1 nodeId:0 name:@"中國" depth:0 expand:YES];
 3 Node *province1 = [[Node alloc] initWithParentId:0 nodeId:1 name:@"江蘇" depth:1 expand:NO];
 4 Node *city1 = [[Node alloc] initWithParentId:1 nodeId:2 name:@"南通" depth:2 expand:NO];
 5 Node *city2 = [[Node alloc] initWithParentId:1 nodeId:3 name:@"南京" depth:2 expand:NO];
 6 Node *city3 = [[Node alloc] initWithParentId:1 nodeId:4 name:@"蘇州" depth:2 expand:NO];
 7 Node *province2 = [[Node alloc] initWithParentId:0 nodeId:5 name:@"廣東" depth:1 expand:NO];
 8 Node *city4 = [[Node alloc] initWithParentId:5 nodeId:6 name:@"深圳" depth:2 expand:NO];
 9 Node *city5 = [[Node alloc] initWithParentId:5 nodeId:7 name:@"廣州" depth:2 expand:NO];
10 Node *province3 = [[Node alloc] initWithParentId:0 nodeId:8 name:@"浙江" depth:1 expand:NO];
11 Node *city6 = [[Node alloc] initWithParentId:8 nodeId:9 name:@"杭州" depth:2 expand:NO];
12 //----------------------------------美國的省地市關係圖0,1,2--------------------------------------------
13 Node *country2 = [[Node alloc] initWithParentId:-1 nodeId:10 name:@"美國" depth:0 expand:YES];
14 Node *province4 = [[Node alloc] initWithParentId:10 nodeId:11 name:@"紐約州" depth:1 expand:NO];
15 Node *province5 = [[Node alloc] initWithParentId:10 nodeId:12 name:@"德州" depth:1 expand:NO];
16 Node *city7 = [[Node alloc] initWithParentId:12 nodeId:13 name:@"休斯頓" depth:2 expand:NO];
17 Node *province6 = [[Node alloc] initWithParentId:10 nodeId:14 name:@"加州" depth:1 expand:NO];
18 Node *city8 = [[Node alloc] initWithParentId:14 nodeId:15 name:@"洛杉磯" depth:2 expand:NO];
19 Node *city9 = [[Node alloc] initWithParentId:14 nodeId:16 name:@"舊金山" depth:2 expand:NO];
20 
21 //----------------------------------日本的省地市關係圖0,1,2--------------------------------------------
22 Node *country3 = [[Node alloc] initWithParentId:-1 nodeId:17 name:@"日本" depth:0 expand:YES];
23 NSArray *data = [NSArray arrayWithObjects:country1,province1,city1,city2,city3,province2,city4,city5,province3,city6,country2,province4,province5,city7,province6,city8,city9,country3, nil];

 

第三步:使用以上數據進行TeeTableView的初始化。

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

 

經過簡單以上三步,你就能夠把該樹形控件集成到你的項目中。網絡

3、實現原理

樹形結構的列表用的其實就是UITableView控件,可是如何可以讓UItableView可以動態的增長和刪除指定的行數的cell是實現樹形結構的關鍵所在。
這時候咱們須要用到兩個UItableView自帶的行數:app

1 - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
2 - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;

 

第一個函數用來在指定的位置插入cells,第二個函數用來在指定的位置刪除cells,而且這二個函數都自帶多種動畫效果,讓刪除和插入的過程不至於太突兀、有種漸變的感受,具備良好的用戶體驗。
對於這幾個動畫作了嘗試:
UITableViewRowAnimationFade : 漸變效果
UITableViewRowAnimationRight : 右邊進入,右邊消失
UITableViewRowAnimationLeft : 左邊進入,左邊消失
UITableViewRowAnimationTop : 頂部進入,頂部消失
UITableViewRowAnimationBottom : 頂部進入,底部消失函數

注意點:學習

在調用insertRowsAtIndexPaths和deleteRowsAtIndexPaths的時候必定要先改變數據源,在調用上述函數,否則會產生crash。動畫

接下來把TreeTableView的主要代碼展現出來,由於原本代碼量就不大,並且代碼中註釋也比較全,但願可以幫助你們理解。ui

  1 #import "TreeTableView.h"
  2 #import "Node.h"
  3 
  4 @interface TreeTableView ()<UITableViewDataSource,UITableViewDelegate>
  5 
  6 @property (nonatomic , strong) NSArray *data;//傳遞過來已經組織好的數據(全量數據)
  7 
  8 @property (nonatomic , strong) NSMutableArray *tempData;//用於存儲數據源(部分數據)
  9 
 10 
 11 @end
 12 
 13 @implementation TreeTableView
 14 
 15 -(instancetype)initWithFrame:(CGRect)frame withData : (NSArray *)data{
 16     self = [super initWithFrame:frame style:UITableViewStyleGrouped];
 17     if (self) {
 18         self.dataSource = self;
 19         self.delegate = self;
 20         _data = data;
 21         _tempData = [self createTempData:data];
 22     }
 23     return self;
 24 }
 25 
 26 /**
 27  * 初始化數據源
 28  */
 29 -(NSMutableArray *)createTempData : (NSArray *)data{
 30     NSMutableArray *tempArray = [NSMutableArray array];
 31     for (int i=0; i<data.count; i++) {
 32         Node *node = [_data objectAtIndex:i];
 33         if (node.expand) {
 34             [tempArray addObject:node];
 35         }
 36     }
 37     return tempArray;
 38 }
 39 
 40 
 41 #pragma mark - UITableViewDataSource
 42 
 43 #pragma mark - Required
 44 
 45 -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
 46     return _tempData.count;
 47 }
 48 
 49 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
 50     static NSString *NODE_CELL_ID = @"node_cell_id";
 51 
 52     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NODE_CELL_ID];
 53     if (!cell) {
 54         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:NODE_CELL_ID];
 55     }
 56 
 57     Node *node = [_tempData objectAtIndex:indexPath.row];
 58 
 59     NSMutableString *name = [NSMutableString string];
 60     for (int i=0; i<node.depth; i++) {
 61         [name appendString:@"     "];
 62     }
 63     [name appendString:node.name];
 64 
 65     cell.textLabel.text = name;
 66 
 67     return cell;
 68 }
 69 
 70 
 71 #pragma mark - Optional
 72 - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
 73     return 0.01;
 74 }
 75 
 76 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
 77     return 40;
 78 }
 79 
 80 - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
 81     return 0.01;
 82 }
 83 
 84 #pragma mark - UITableViewDelegate
 85 
 86 #pragma mark - Optional
 87 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
 88     //先修改數據源
 89     Node *parentNode = [_tempData objectAtIndex:indexPath.row];
 90     NSUInteger startPosition = indexPath.row+1;
 91     NSUInteger endPosition = startPosition;
 92     BOOL expand = NO;
 93     for (int i=0; i<_data.count; i++) {
 94         Node *node = [_data objectAtIndex:i];
 95         if (node.parentId == parentNode.nodeId) {
 96             node.expand = !node.expand;
 97             if (node.expand) {
 98                 [_tempData insertObject:node atIndex:endPosition];
 99                 expand = YES;
100             }else{
101                 expand = NO;
102                 endPosition = [self removeAllNodesAtParentNode:parentNode];
103                 break;
104             }
105             endPosition++;
106         }
107     }
108 
109     //得到須要修正的indexPath
110     NSMutableArray *indexPathArray = [NSMutableArray array];
111     for (NSUInteger i=startPosition; i<endPosition; i++) {
112         NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
113         [indexPathArray addObject:tempIndexPath];
114     }
115 
116     //插入或者刪除相關節點
117     if (expand) {
118         [self insertRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationNone];
119     }else{
120         [self deleteRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationNone];
121     }
122 }
123 
124 /**
125  *  刪除該父節點下的全部子節點(包括孫子節點)
126  *
127  *  @param parentNode 父節點
128  *
129  *  @return 鄰接父節點的位置距離該父節點的長度,也就是該父節點下面全部的子孫節點的數量
130  */
131 -(NSUInteger)removeAllNodesAtParentNode : (Node *)parentNode{
132     NSUInteger startPosition = [_tempData indexOfObject:parentNode];
133     NSUInteger endPosition = startPosition;
134     for (NSUInteger i=startPosition+1; i<_tempData.count; i++) {
135         Node *node = [_tempData objectAtIndex:i];
136         endPosition++;
137         if (node.depth == parentNode.depth) {
138             break;
139         }
140         node.expand = NO;
141     }
142     if (endPosition>startPosition) {
143         [_tempData removeObjectsInRange:NSMakeRange(startPosition+1, endPosition-startPosition-1)];
144     }
145     return endPosition;
146 }

 

4、總結

在演示項目中,每一個cell我都使用系統自帶的cell,樣式比較簡單,若是你要展示更加漂亮的樣式,能夠自定義cell。
同時,你也能夠擴展該數據模型,運動到更加複雜的業務處理中。好比如下場景:atom

 

 

5、下載地址

Demo下載地址:這是一個個人iOS交流羣:624212887,羣文件自行下載,無論你是小白仍是大牛熱烈歡迎進羣 ,分享面試經驗,討論技術, 你們一塊兒交流學習成長!但願幫助開發者少走彎路。spa

若是以爲對你還有些用,就關注小編+喜歡這一篇文章。你的支持是我繼續的動力。

下篇文章預告:iOS開發UI篇--一個支持圖文混排的ActionSheet

文章來源於網絡,若有侵權,請聯繫小編刪除。

相關文章
相關標籤/搜索