A TreeView Template that you can make deep customization,With the two tree templates provided, you can accomplish most of your business needs。
這是一個可以進行深度自定義的樹形結構模板,通過提供的兩個樹形模板,基本可以完成大部分業務需求。
一、功能簡介
1、支援兩種常見的樹形結構
一種是向下無限展開式的樹形結構(ExpansionStyle
),另一種是麵包屑形式的樹形結構(BreadcrumbsStyle
)。
2、支援自定義nodeModel
節點模型,自定義nodeView
節點檢視,自定義node
節點的高度
本質上無需繼承,任意模型與檢視都可以拿來構成一顆樹,只要遵守相對應的NodeModelProtocol
和NodeViewProtocol
協議,自己實現相對應的屬性與方法即可,當然,也可以直接繼承模板提供的節點模型基類,或者直接繼承協議,自定義一個新的協議。
3、支援縮排
4、支援自動重新整理與手動重新整理兩種方式
分別對應本地資料來源與網路資料來源,同時可以指定樹的展開動畫RowAnimation
。建議使用手動重新整理,這也是預設方式。
5、支援自動佈局
在nodeView
高度發生變化或者設定了縮排,會自動遞迴的向所有的subview
傳送setNeedLayout
訊息,可以在需要重新佈局的子檢視中重寫layoutSubviews
進行重新佈局。
6、節點模型基類BaseTreeNode
提供的一些輔助功能:
- 自動遞迴計算樹的根節點
- 自動遞迴計算樹的高度
- 自動遞迴計算該節點所在的層級,預設根節點的層級為0
- 其他基本操作
二、如何使用
安裝
- 手動安裝:將
TreeViewTemplate
資料夾拖入專案 - CocoaPod:podfile加入
pod `TreeViewTemplate`
(待完善)
使用
1、建立NodeTreeView
/**
初始化方法
@param frame frame
@param style 展現形式:BreadcrumbsStyle或者ExpansionStyle
@return treeView例項
*/
- (instancetype _Nullable )initWithFrame:(CGRect)frame
treeViewStyle:(NodeTreeViewStyle)style;
複製程式碼
2、設定代理
@protocol NodeTreeViewDelegate
@required
/**
返回對應節點下的檢視:檢視可以遵循NodeViewProtocol協議,讓view具有一些統一的行為>
一種node對應一種nodeView
@param node node節點
@return node檢視
*/
- (id<NodeViewProtocol>_Nonnull)nodeTreeView:(NodeTreeView *_Nonnull)treeView viewForNode:(id<NodeModelProtocol>_Nonnull)node;
@optional
/**
返回對應級別下的縮排
@param treeView treeView
@param nodeLevel 對應的nodeLevel
@return 該level下對應的縮排
*/
- (CGFloat)nodeTreeView:(NodeTreeView *_Nonnull)treeView indentAtNodeLevel:(NSInteger)nodeLevel;
/**
點選事件回撥
@param treeView 樹
@param node 節點模型
*/
- (void)nodeTreeView:(NodeTreeView *_Nonnull)treeView didSelectNode:(id<NodeModelProtocol>_Nonnull)node;
@end
複製程式碼
3、設定是否需要自動重新整理,不建議使用自動重新整理
/**
樹的重新整理策略
預設是手動重新整理:NodeTreeRefreshPolicyManaul
*/
@property (nonatomic, assign) NodeTreeRefreshPolicy refreshPolicy;
複製程式碼
4、如果資料是實時獲得的,那麼需要手動呼叫重新整理方法
/**
重新整理node節點對應的樹
*/
- (void)reloadTreeViewWithNode:(id<NodeModelProtocol>_Nonnull)node;
/**
重新整理node節點對應的樹,可以指定動畫展開的方式
@param node node節點
*/
- (void)reloadTreeViewWithNode:(id<NodeModelProtocol>_Nonnull)node
RowAnimation:(UITableViewRowAnimation)animation;
複製程式碼
5、使用時建議將treeView作為一個cell,放在一個tableview中使用。
6、關於NodeModelProtocol
節點模型協議
- 宣告瞭一些遵守該協議的模型需要手動實現的屬性和方法。
- 屬性宣告:節點高度、子節點陣列、父節點、節點所在的層級、節點展開後的所有兒子節點的高度之和、該節點所在樹的當前整體高度、節點是否展開。
- 方法宣告:增加節點、從陣列中增加節點、刪除節點。
7、關於NodeViewProtocol
節點檢視協議
- 定義了所有節點檢視必須實現的方法,如下所示:
@protocol NodeViewProtocol
@required
/**
更新單個Node行
@param node node模型
*/
- (void)updateNodeViewWithNodeModel:(id<NodeModelProtocol>)node;
/**
需要在該方法中,對view進行重新佈局,為了處理在縮排的時候寬度變化造成的影響
*/
- (void)layoutSubviews;
@end
複製程式碼
三、對遞迴的使用
在處理樹的時候,用到的一些遞迴操作:
==================== NodeTreeView.m中對遞迴的使用 ====================
1、#pragma mark NodeTreeViewStyleExpansion模式下,初始化資料來源,遞迴的將所有需要展開的節點,加入到初始資料來源中
static inline void RecursiveInitializeAllNodesWithRootNode(NSMutableArray *allNodes,id<NodeModelProtocol>rootNode){
if (rootNode.expand == NO || rootNode.subNodes.count == 0) {
return;
}else{
if (allNodes.count == 0) {
[allNodes addObjectsFromArray:rootNode.subNodes];
}else{
NSUInteger beginPosition = [allNodes indexOfObject:rootNode] + 1;
NSUInteger endPosition = beginPosition + rootNode.subNodes.count - 1;
NSRange range = NSMakeRange(beginPosition, endPosition-beginPosition+1);
NSIndexSet *set = [NSIndexSet indexSetWithIndexesInRange:range];
[allNodes insertObjects:rootNode.subNodes atIndexes:set];
}
for (id<NodeModelProtocol>subNode in rootNode.subNodes) {
rootNode = subNode;
RecursiveInitializeAllNodesWithRootNode(allNodes, rootNode);
}
}
}
2、#pragma mark 遞迴的將某節點下所有子節點的展開狀態置為NO
static inline void RecursiveFoldAllSubnodesAtNode(id<NodeModelProtocol>node){
if (node.subNodes.count>0) {
[node.subNodes enumerateObjectsUsingBlock:^(id<NodeModelProtocol> _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj.isExpand) {
obj.expand = NO;
RecursiveFoldAllSubnodesAtNode(node);
}
}];
}else{
return;
}
}
3、#pragma mark 遞迴的向view的所有子view傳送setNeedsLayout訊息,進行重新佈局
static inline void RecursiveLayoutSubviews(UIView *_Nonnull view){
if (view.subviews.count == 0) {
return;
}else{
[view.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull subView, NSUInteger idx, BOOL * _Nonnull stop) {
[subView setNeedsLayout];
RecursiveLayoutSubviews(subView);
}];
}
}
==================== BaseTreeNode.m中對遞迴的使用 ====================
CGFloat treeHeight;
CGFloat tempNodeLevel;
4、#pragma mark 獲取根節點
static inline id<NodeModelProtocol>RecursiveGetRootNodeWithNode(id<NodeModelProtocol> node){
if (node.fatherNode == node) {
node.expand = YES;
return node;
}else{
node = node.fatherNode;
tempNodeLevel = tempNodeLevel+1;
return RecursiveGetRootNodeWithNode(node);
}
}
5、#pragma mark 根據根節點獲取樹的高度
static inline void RecursiveCalculateTreeHeightWithRootNode(id<NodeModelProtocol> rootNode){
if (rootNode.subNodes.count == 0||!rootNode.isExpand) {
return ;
}
if (!isnan(rootNode.subTreeHeight)) {
treeHeight += rootNode.subTreeHeight;
}
for (id<NodeModelProtocol>obj in rootNode.subNodes) {
RecursiveCalculateTreeHeightWithRootNode(obj);
}
}
複製程式碼
四、效果展示
1、麵包屑模式-自動
2、麵包屑模式-手動
3、Expansion模式-自動
4、Expansion模式-手動
GitHub下載地址:TreeViewTemplate