TreeViewTemplate移動元件詳細介紹

ccSunday發表於2019-02-25

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節點的高度

本質上無需繼承,任意模型與檢視都可以拿來構成一顆樹,只要遵守相對應的NodeModelProtocolNodeViewProtocol協議,自己實現相對應的屬性與方法即可,當然,也可以直接繼承模板提供的節點模型基類,或者直接繼承協議,自定義一個新的協議。

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模式-自動

Expansion模式-自動

4、Expansion模式-手動

Expansion模式-手動

GitHub下載地址:TreeViewTemplate

相關文章