├── README.md ├── TreeNodeStructure.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── ccsunday.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── TreeNodeStructure ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── next.imageset │ │ ├── Contents.json │ │ ├── d_next@2x.png │ │ └── d_next@3x.png │ ├── node_normal.imageset │ │ ├── Contents.json │ │ ├── jiaqian_xuanzhe_icon_@2x.png │ │ └── jiaqian_xuanzhe_icon_@3x.png │ └── node_selected.imageset │ │ ├── Contents.json │ │ ├── jiaqian_xuanzhong_icon_@2x.png │ │ └── jiaqian_xuanzhong_icon_@3x.png ├── AssistMicros.h ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Examples │ ├── Controller │ │ ├── AutoRefresh │ │ │ ├── AutoBreadcrumbViewController.h │ │ │ ├── AutoBreadcrumbViewController.m │ │ │ ├── AutoExpansionViewController.h │ │ │ └── AutoExpansionViewController.m │ │ ├── Base │ │ │ ├── BaseViewController.h │ │ │ └── BaseViewController.m │ │ └── ManaulRefresh │ │ │ ├── BreadcrumbViewController.h │ │ │ ├── BreadcrumbViewController.m │ │ │ ├── ExpansionViewController.h │ │ │ └── ExpansionViewController.m │ ├── CustomProtocols │ │ ├── CustomNodeModelProtocol.h │ │ └── CustomNodeViewProtocol.h │ ├── Model │ │ ├── OrganizationNode.h │ │ ├── OrganizationNode.m │ │ ├── SinglePersonNode.h │ │ └── SinglePersonNode.m │ └── View │ │ ├── Cells │ │ ├── TreeOrganizationDisplayCell.h │ │ └── TreeOrganizationDisplayCell.m │ │ ├── NodeViews │ │ ├── OrganizationNodeView.h │ │ ├── OrganizationNodeView.m │ │ ├── SinglePersonNodeView.h │ │ └── SinglePersonNodeView.m │ │ └── Others │ │ ├── BreadcrumbHeaderView.h │ │ └── BreadcrumbHeaderView.m ├── Info.plist ├── TreeViewTemplate │ ├── Nodes │ │ ├── BaseTreeNode.h │ │ └── BaseTreeNode.m │ ├── Protocols │ │ ├── NodeModelProtocol.h │ │ └── NodeViewProtocol.h │ └── Views │ │ ├── NodeTreeView.h │ │ └── NodeTreeView.m ├── ViewController.h ├── ViewController.m └── main.m ├── TreeNodeStructureTests ├── Info.plist └── TreeNodeStructureTests.m └── TreeNodeStructureUITests ├── Info.plist └── TreeNodeStructureUITests.m /README.md: -------------------------------------------------------------------------------- 1 | # TreeViewTemplate 2 | 3 | > A TreeView Template that you can make deep customization,With the two tree templates provided, you can accomplish most of your business needs。 4 | 5 | > 这是一个可以进行深度自定义的树形结构模板,通过提供的两个树形模板,基本可以完成大部分业务需求。 6 | 7 | 8 | ## 一、功能简介 9 | 10 | #### 1、支持两种常见的树形结构 11 | 12 | 一种是向下无限展开式的树形结构(`ExpansionStyle`),另一种是面包屑形式的树形结构(`BreadcrumbsStyle`)。 13 | 14 | #### 2、支持自定义`nodeModel`节点模型,自定义`nodeView`节点视图,自定义`node`节点的高度 15 | 16 | 本质上无需继承,任意模型与视图都可以拿来构成一颗树,只要遵守相对应的`NodeModelProtocol`和`NodeViewProtocol`协议,自己实现相对应的属性与方法即可,当然,也可以直接继承模板提供的节点模型基类,或者直接继承协议,自定义一个新的协议。 17 | 18 | #### 3、支持缩进 19 | 20 | #### 4、支持自动刷新与手动刷新两种方式 21 | 分别对应本地数据源与网络数据源,同时可以指定树的展开动画`RowAnimation`。建议使用手动刷新,这也是默认方式。 22 | 23 | #### 5、支持自动布局 24 | 在`nodeView`高度发生变化或者设置了缩进,会自动递归的向所有的`subview`发送`setNeedLayout`消息,可以在需要重新布局的子视图中重写`layoutSubviews`进行重新布局。 25 | 26 | #### 6、节点模型基类`BaseTreeNode`提供的一些辅助功能: 27 | * 自动递归计算树的根节点 28 | * 自动递归计算树的高度 29 | * 自动递归计算该节点所在的层级,默认根节点的层级为0 30 | * 其他基本操作 31 | 32 | ## 二、如何使用 33 | ### 安装 34 | * 手动安装:将`TreeViewTemplate`文件夹拖入项目 35 | * CocoaPod:podfile加入`pod 'TreeViewTemplate'`(待完善) 36 | 37 | ### 使用 38 | #### 1、创建`NodeTreeView` 39 | ``` 40 | /** 41 | 初始化方法 42 | 43 | @param frame frame 44 | @param style 展现形式:BreadcrumbsStyle或者ExpansionStyle 45 | @return treeView实例 46 | */ 47 | - (instancetype _Nullable )initWithFrame:(CGRect)frame 48 | treeViewStyle:(NodeTreeViewStyle)style; 49 | 50 | ``` 51 | #### 2、设置代理 52 | ``` 53 | @protocol NodeTreeViewDelegate 54 | @required 55 | /** 56 | 返回对应节点下的视图:视图可以遵循NodeViewProtocol协议,让view具有一些统一的行为> 57 | 一种node对应一种nodeView 58 | 59 | @param node node节点 60 | @return node视图 61 | */ 62 | - (id_Nonnull)nodeTreeView:(NodeTreeView *_Nonnull)treeView viewForNode:(id_Nonnull)node; 63 | 64 | @optional 65 | 66 | /** 67 | 返回对应级别下的缩进 68 | 69 | @param treeView treeView 70 | @param nodeLevel 对应的nodeLevel 71 | @return 该level下对应的缩进 72 | */ 73 | - (CGFloat)nodeTreeView:(NodeTreeView *_Nonnull)treeView indentAtNodeLevel:(NSInteger)nodeLevel; 74 | /** 75 | 点击事件回调 76 | 77 | @param treeView 树 78 | @param node 节点模型 79 | */ 80 | - (void)nodeTreeView:(NodeTreeView *_Nonnull)treeView didSelectNode:(id_Nonnull)node; 81 | 82 | @end 83 | 84 | ``` 85 | #### 3、设置是否需要自动刷新,不建议使用自动刷新 86 | ``` 87 | /** 88 | 树的刷新策略 89 | 默认是手动刷新:NodeTreeRefreshPolicyManaul 90 | */ 91 | @property (nonatomic, assign) NodeTreeRefreshPolicy refreshPolicy; 92 | 93 | ``` 94 | #### 4、如果数据是实时获得的,那么需要手动调用刷新方法 95 | ``` 96 | /** 97 | 刷新node节点对应的树 98 | */ 99 | - (void)reloadTreeViewWithNode:(id_Nonnull)node; 100 | 101 | /** 102 | 刷新node节点对应的树,可以指定动画展开的方式 103 | @param node node节点 104 | */ 105 | - (void)reloadTreeViewWithNode:(id_Nonnull)node 106 | RowAnimation:(UITableViewRowAnimation)animation; 107 | 108 | ``` 109 | #### 5、使用时建议将treeView作为一个cell,放在一个tableview中使用。 110 | 111 | #### 6、关于`NodeModelProtocol`节点模型协议 112 | * 声明了一些遵守该协议的模型需要手动实现的属性和方法。 113 | * 属性声明:节点高度、子节点数组、父节点、节点所在的层级、节点展开后的所有儿子节点的高度之和、该节点所在树的当前整体高度、节点是否展开。 114 | * 方法声明:增加节点、从数组中增加节点、删除节点。 115 | 116 | #### 7、关于`NodeViewProtocol`节点模型协议 117 | * 定义了所有节点视图必须实现的方法,如下所示: 118 | ``` 119 | 120 | @protocol NodeViewProtocol 121 | @required 122 | /** 123 | 更新单个Node行 124 | 125 | @param node node模型 126 | */ 127 | - (void)updateNodeViewWithNodeModel:(id)node; 128 | /** 129 | 需要在该方法中,对cell进行重新布局,为了处理在缩进的时候宽度变化造成的影响 130 | */ 131 | - (void)layoutSubviews; 132 | 133 | @end 134 | ``` 135 | 136 | ## 三、对递归的使用 137 | 138 | 在处理树的时候,用到的一些递归操作: 139 | 140 | ``` 141 | ==================== NodeTreeView.m中对递归的使用 ==================== 142 | 1、#pragma mark NodeTreeViewStyleExpansion模式下,初始化数据源,递归的将所有需要展开的节点,加入到初始数据源中 143 | 144 | static inline void RecursiveInitializeAllNodesWithRootNode(NSMutableArray *allNodes,idrootNode){ 145 | if (rootNode.expand == NO || rootNode.subNodes.count == 0) { 146 | return; 147 | }else{ 148 | if (allNodes.count == 0) { 149 | [allNodes addObjectsFromArray:rootNode.subNodes]; 150 | }else{ 151 | NSUInteger beginPosition = [allNodes indexOfObject:rootNode] + 1; 152 | NSUInteger endPosition = beginPosition + rootNode.subNodes.count - 1; 153 | NSRange range = NSMakeRange(beginPosition, endPosition-beginPosition+1); 154 | NSIndexSet *set = [NSIndexSet indexSetWithIndexesInRange:range]; 155 | [allNodes insertObjects:rootNode.subNodes atIndexes:set]; 156 | } 157 | for (idsubNode in rootNode.subNodes) { 158 | rootNode = subNode; 159 | RecursiveInitializeAllNodesWithRootNode(allNodes, rootNode); 160 | } 161 | } 162 | } 163 | 164 | 2、#pragma mark 递归的将某节点下所有子节点的展开状态置为NO 165 | 166 | static inline void RecursiveFoldAllSubnodesAtNode(idnode){ 167 | 168 | if (node.subNodes.count>0) { 169 | [node.subNodes enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 170 | if (obj.isExpand) { 171 | obj.expand = NO; 172 | RecursiveFoldAllSubnodesAtNode(node); 173 | }}]; 174 | }else{ 175 | return; 176 | } 177 | } 178 | 179 | 3、#pragma mark 递归的向view的所有子view发送setNeedsLayout消息,进行重新布局 180 | 181 | static inline void RecursiveLayoutSubviews(UIView *_Nonnull view){ 182 | if (view.subviews.count == 0) { 183 | return; 184 | }else{ 185 | [view.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull subView, NSUInteger idx, BOOL * _Nonnull stop) { 186 | [subView setNeedsLayout]; 187 | RecursiveLayoutSubviews(subView); 188 | }]; 189 | } 190 | } 191 | 192 | ==================== BaseTreeNode.m中对递归的使用 ==================== 193 | CGFloat treeHeight; 194 | 195 | CGFloat tempNodeLevel; 196 | 197 | 4、#pragma mark 获取根节点 198 | 199 | static inline idRecursiveGetRootNodeWithNode(id node){ 200 | if (node.fatherNode == node) { 201 | node.expand = YES; 202 | return node; 203 | }else{ 204 | node = node.fatherNode; 205 | tempNodeLevel = tempNodeLevel+1; 206 | return RecursiveGetRootNodeWithNode(node); 207 | } 208 | } 209 | 210 | 5、#pragma mark 根据根节点获取树的高度 211 | 212 | static inline void RecursiveCalculateTreeHeightWithRootNode(id rootNode){ 213 | if (rootNode.subNodes.count == 0||!rootNode.isExpand) { 214 | return ; 215 | } 216 | if (!isnan(rootNode.subTreeHeight)) { 217 | treeHeight += rootNode.subTreeHeight; 218 | } 219 | for (idobj in rootNode.subNodes) { 220 | RecursiveCalculateTreeHeightWithRootNode(obj); 221 | } 222 | } 223 | 224 | ``` 225 | ## 四、效果展示 226 | 227 | 1、面包屑模式-自动 228 | 229 | ![面包屑模式-自动](https://user-gold-cdn.xitu.io/2018/1/31/1614ba34ac5701fc?w=285&h=428&f=gif&s=493107) 230 | 231 | 2、面包屑模式-手动 232 | 233 | ![面包屑模式-手动](https://user-gold-cdn.xitu.io/2018/1/31/1614ba3e54dd43d0?w=299&h=449&f=gif&s=468026) 234 | 235 | 3、Expansion模式-自动 236 | 237 | ![Expansion模式-自动](https://user-gold-cdn.xitu.io/2018/1/31/1614ba44592cab16?w=241&h=362&f=gif&s=515876) 238 | 239 | 4、Expansion模式-手动 240 | 241 | ![Expansion模式-手动](https://user-gold-cdn.xitu.io/2018/1/31/1614ba4a344fce1c?w=214&h=321&f=gif&s=522766) 242 | 243 | 244 | GitHub下载地址:[TreeViewTemplate](https://github.com/sunday1990/TreeViewTemplate) 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /TreeNodeStructure.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 16340A4220204B5F0026E47F /* BreadcrumbViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 16340A4120204B5F0026E47F /* BreadcrumbViewController.m */; }; 11 | 16340A4520204B6A0026E47F /* ExpansionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 16340A4420204B6A0026E47F /* ExpansionViewController.m */; }; 12 | 16340A5920216BFE0026E47F /* BaseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 16340A5820216BFE0026E47F /* BaseViewController.m */; }; 13 | 16340A5C20216C5C0026E47F /* AutoBreadcrumbViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 16340A5B20216C5C0026E47F /* AutoBreadcrumbViewController.m */; }; 14 | 16340A5F20216C7D0026E47F /* AutoExpansionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 16340A5E20216C7D0026E47F /* AutoExpansionViewController.m */; }; 15 | 169F23FD2016CECE00793E22 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 169F23FC2016CECE00793E22 /* AppDelegate.m */; }; 16 | 169F24002016CECE00793E22 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 169F23FF2016CECE00793E22 /* ViewController.m */; }; 17 | 169F24032016CECE00793E22 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 169F24012016CECE00793E22 /* Main.storyboard */; }; 18 | 169F24052016CECE00793E22 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 169F24042016CECE00793E22 /* Assets.xcassets */; }; 19 | 169F24082016CECE00793E22 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 169F24062016CECE00793E22 /* LaunchScreen.storyboard */; }; 20 | 169F240B2016CECE00793E22 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 169F240A2016CECE00793E22 /* main.m */; }; 21 | 169F24152016CECE00793E22 /* TreeNodeStructureTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 169F24142016CECE00793E22 /* TreeNodeStructureTests.m */; }; 22 | 169F24202016CECE00793E22 /* TreeNodeStructureUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 169F241F2016CECE00793E22 /* TreeNodeStructureUITests.m */; }; 23 | 169F24302016CF6800793E22 /* BaseTreeNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 169F242F2016CF6800793E22 /* BaseTreeNode.m */; }; 24 | 169F24372016E6CE00793E22 /* OrganizationNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 169F24352016E6CE00793E22 /* OrganizationNode.m */; }; 25 | 169F243A2016E72000793E22 /* SinglePersonNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 169F24392016E72000793E22 /* SinglePersonNode.m */; }; 26 | 169F24422016FC7000793E22 /* NodeTreeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 169F24412016FC7000793E22 /* NodeTreeView.m */; }; 27 | 169F24482017064900793E22 /* SinglePersonNodeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 169F24472017064900793E22 /* SinglePersonNodeView.m */; }; 28 | 169F244E2017106D00793E22 /* OrganizationNodeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 169F244D2017106D00793E22 /* OrganizationNodeView.m */; }; 29 | 169F2451201736F500793E22 /* BreadcrumbHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 169F2450201736F500793E22 /* BreadcrumbHeaderView.m */; }; 30 | 169F245420173F5500793E22 /* TreeOrganizationDisplayCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 169F245320173F5500793E22 /* TreeOrganizationDisplayCell.m */; }; 31 | /* End PBXBuildFile section */ 32 | 33 | /* Begin PBXContainerItemProxy section */ 34 | 169F24112016CECE00793E22 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = 169F23F02016CECE00793E22 /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = 169F23F72016CECE00793E22; 39 | remoteInfo = TreeNodeStructure; 40 | }; 41 | 169F241C2016CECE00793E22 /* PBXContainerItemProxy */ = { 42 | isa = PBXContainerItemProxy; 43 | containerPortal = 169F23F02016CECE00793E22 /* Project object */; 44 | proxyType = 1; 45 | remoteGlobalIDString = 169F23F72016CECE00793E22; 46 | remoteInfo = TreeNodeStructure; 47 | }; 48 | /* End PBXContainerItemProxy section */ 49 | 50 | /* Begin PBXFileReference section */ 51 | 16340A4020204B5F0026E47F /* BreadcrumbViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BreadcrumbViewController.h; sourceTree = ""; }; 52 | 16340A4120204B5F0026E47F /* BreadcrumbViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BreadcrumbViewController.m; sourceTree = ""; }; 53 | 16340A4320204B6A0026E47F /* ExpansionViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExpansionViewController.h; sourceTree = ""; }; 54 | 16340A4420204B6A0026E47F /* ExpansionViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExpansionViewController.m; sourceTree = ""; }; 55 | 16340A4D20204DDA0026E47F /* AssistMicros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AssistMicros.h; sourceTree = ""; }; 56 | 16340A5720216BFE0026E47F /* BaseViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BaseViewController.h; sourceTree = ""; }; 57 | 16340A5820216BFE0026E47F /* BaseViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BaseViewController.m; sourceTree = ""; }; 58 | 16340A5A20216C5C0026E47F /* AutoBreadcrumbViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AutoBreadcrumbViewController.h; sourceTree = ""; }; 59 | 16340A5B20216C5C0026E47F /* AutoBreadcrumbViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AutoBreadcrumbViewController.m; sourceTree = ""; }; 60 | 16340A5D20216C7D0026E47F /* AutoExpansionViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AutoExpansionViewController.h; sourceTree = ""; }; 61 | 16340A5E20216C7D0026E47F /* AutoExpansionViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AutoExpansionViewController.m; sourceTree = ""; }; 62 | 16340A61202189930026E47F /* CustomNodeModelProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CustomNodeModelProtocol.h; sourceTree = ""; }; 63 | 16340A6220218AD50026E47F /* CustomNodeViewProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CustomNodeViewProtocol.h; sourceTree = ""; }; 64 | 169F23F82016CECE00793E22 /* TreeNodeStructure.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TreeNodeStructure.app; sourceTree = BUILT_PRODUCTS_DIR; }; 65 | 169F23FB2016CECE00793E22 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 66 | 169F23FC2016CECE00793E22 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 67 | 169F23FE2016CECE00793E22 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 68 | 169F23FF2016CECE00793E22 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 69 | 169F24022016CECE00793E22 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 70 | 169F24042016CECE00793E22 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 71 | 169F24072016CECE00793E22 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 72 | 169F24092016CECE00793E22 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 73 | 169F240A2016CECE00793E22 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 74 | 169F24102016CECE00793E22 /* TreeNodeStructureTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TreeNodeStructureTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 75 | 169F24142016CECE00793E22 /* TreeNodeStructureTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TreeNodeStructureTests.m; sourceTree = ""; }; 76 | 169F24162016CECE00793E22 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 77 | 169F241B2016CECE00793E22 /* TreeNodeStructureUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TreeNodeStructureUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 78 | 169F241F2016CECE00793E22 /* TreeNodeStructureUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TreeNodeStructureUITests.m; sourceTree = ""; }; 79 | 169F24212016CECE00793E22 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 80 | 169F242E2016CF6800793E22 /* BaseTreeNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BaseTreeNode.h; sourceTree = ""; }; 81 | 169F242F2016CF6800793E22 /* BaseTreeNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BaseTreeNode.m; sourceTree = ""; }; 82 | 169F24342016DB7C00793E22 /* NodeModelProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NodeModelProtocol.h; sourceTree = ""; }; 83 | 169F24352016E6CE00793E22 /* OrganizationNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OrganizationNode.m; sourceTree = ""; }; 84 | 169F24362016E6CE00793E22 /* OrganizationNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OrganizationNode.h; sourceTree = ""; }; 85 | 169F24382016E72000793E22 /* SinglePersonNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SinglePersonNode.h; sourceTree = ""; }; 86 | 169F24392016E72000793E22 /* SinglePersonNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SinglePersonNode.m; sourceTree = ""; }; 87 | 169F24402016FC7000793E22 /* NodeTreeView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NodeTreeView.h; sourceTree = ""; }; 88 | 169F24412016FC7000793E22 /* NodeTreeView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NodeTreeView.m; sourceTree = ""; }; 89 | 169F2444201704CF00793E22 /* NodeViewProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NodeViewProtocol.h; sourceTree = ""; }; 90 | 169F24462017064900793E22 /* SinglePersonNodeView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SinglePersonNodeView.h; sourceTree = ""; }; 91 | 169F24472017064900793E22 /* SinglePersonNodeView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SinglePersonNodeView.m; sourceTree = ""; }; 92 | 169F244C2017106D00793E22 /* OrganizationNodeView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OrganizationNodeView.h; sourceTree = ""; }; 93 | 169F244D2017106D00793E22 /* OrganizationNodeView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OrganizationNodeView.m; sourceTree = ""; }; 94 | 169F244F201736F500793E22 /* BreadcrumbHeaderView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BreadcrumbHeaderView.h; sourceTree = ""; }; 95 | 169F2450201736F500793E22 /* BreadcrumbHeaderView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BreadcrumbHeaderView.m; sourceTree = ""; }; 96 | 169F245220173F5500793E22 /* TreeOrganizationDisplayCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TreeOrganizationDisplayCell.h; sourceTree = ""; }; 97 | 169F245320173F5500793E22 /* TreeOrganizationDisplayCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TreeOrganizationDisplayCell.m; sourceTree = ""; }; 98 | /* End PBXFileReference section */ 99 | 100 | /* Begin PBXFrameworksBuildPhase section */ 101 | 169F23F52016CECE00793E22 /* Frameworks */ = { 102 | isa = PBXFrameworksBuildPhase; 103 | buildActionMask = 2147483647; 104 | files = ( 105 | ); 106 | runOnlyForDeploymentPostprocessing = 0; 107 | }; 108 | 169F240D2016CECE00793E22 /* Frameworks */ = { 109 | isa = PBXFrameworksBuildPhase; 110 | buildActionMask = 2147483647; 111 | files = ( 112 | ); 113 | runOnlyForDeploymentPostprocessing = 0; 114 | }; 115 | 169F24182016CECE00793E22 /* Frameworks */ = { 116 | isa = PBXFrameworksBuildPhase; 117 | buildActionMask = 2147483647; 118 | files = ( 119 | ); 120 | runOnlyForDeploymentPostprocessing = 0; 121 | }; 122 | /* End PBXFrameworksBuildPhase section */ 123 | 124 | /* Begin PBXGroup section */ 125 | 16340A4620204B810026E47F /* View */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 16340A4B20204BDE0026E47F /* Others */, 129 | 16340A4A20204BC10026E47F /* Cells */, 130 | 16340A4920204BB60026E47F /* NodeViews */, 131 | ); 132 | path = View; 133 | sourceTree = ""; 134 | }; 135 | 16340A4720204B8C0026E47F /* Model */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 169F24382016E72000793E22 /* SinglePersonNode.h */, 139 | 169F24392016E72000793E22 /* SinglePersonNode.m */, 140 | 169F24362016E6CE00793E22 /* OrganizationNode.h */, 141 | 169F24352016E6CE00793E22 /* OrganizationNode.m */, 142 | ); 143 | path = Model; 144 | sourceTree = ""; 145 | }; 146 | 16340A4820204B910026E47F /* Controller */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | 16340A5220216AD70026E47F /* Base */, 150 | 16340A5320216ADF0026E47F /* AutoRefresh */, 151 | 16340A5120216ABF0026E47F /* ManaulRefresh */, 152 | ); 153 | path = Controller; 154 | sourceTree = ""; 155 | }; 156 | 16340A4920204BB60026E47F /* NodeViews */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 169F24462017064900793E22 /* SinglePersonNodeView.h */, 160 | 169F24472017064900793E22 /* SinglePersonNodeView.m */, 161 | 169F244C2017106D00793E22 /* OrganizationNodeView.h */, 162 | 169F244D2017106D00793E22 /* OrganizationNodeView.m */, 163 | ); 164 | path = NodeViews; 165 | sourceTree = ""; 166 | }; 167 | 16340A4A20204BC10026E47F /* Cells */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | 169F245220173F5500793E22 /* TreeOrganizationDisplayCell.h */, 171 | 169F245320173F5500793E22 /* TreeOrganizationDisplayCell.m */, 172 | ); 173 | path = Cells; 174 | sourceTree = ""; 175 | }; 176 | 16340A4B20204BDE0026E47F /* Others */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | 169F244F201736F500793E22 /* BreadcrumbHeaderView.h */, 180 | 169F2450201736F500793E22 /* BreadcrumbHeaderView.m */, 181 | ); 182 | path = Others; 183 | sourceTree = ""; 184 | }; 185 | 16340A5120216ABF0026E47F /* ManaulRefresh */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | 16340A4020204B5F0026E47F /* BreadcrumbViewController.h */, 189 | 16340A4120204B5F0026E47F /* BreadcrumbViewController.m */, 190 | 16340A4320204B6A0026E47F /* ExpansionViewController.h */, 191 | 16340A4420204B6A0026E47F /* ExpansionViewController.m */, 192 | ); 193 | path = ManaulRefresh; 194 | sourceTree = ""; 195 | }; 196 | 16340A5220216AD70026E47F /* Base */ = { 197 | isa = PBXGroup; 198 | children = ( 199 | 16340A5720216BFE0026E47F /* BaseViewController.h */, 200 | 16340A5820216BFE0026E47F /* BaseViewController.m */, 201 | ); 202 | path = Base; 203 | sourceTree = ""; 204 | }; 205 | 16340A5320216ADF0026E47F /* AutoRefresh */ = { 206 | isa = PBXGroup; 207 | children = ( 208 | 16340A5A20216C5C0026E47F /* AutoBreadcrumbViewController.h */, 209 | 16340A5B20216C5C0026E47F /* AutoBreadcrumbViewController.m */, 210 | 16340A5D20216C7D0026E47F /* AutoExpansionViewController.h */, 211 | 16340A5E20216C7D0026E47F /* AutoExpansionViewController.m */, 212 | ); 213 | path = AutoRefresh; 214 | sourceTree = ""; 215 | }; 216 | 16340A602021896F0026E47F /* CustomProtocols */ = { 217 | isa = PBXGroup; 218 | children = ( 219 | 16340A61202189930026E47F /* CustomNodeModelProtocol.h */, 220 | 16340A6220218AD50026E47F /* CustomNodeViewProtocol.h */, 221 | ); 222 | path = CustomProtocols; 223 | sourceTree = ""; 224 | }; 225 | 169F23EF2016CECE00793E22 = { 226 | isa = PBXGroup; 227 | children = ( 228 | 169F23FA2016CECE00793E22 /* TreeNodeStructure */, 229 | 169F24132016CECE00793E22 /* TreeNodeStructureTests */, 230 | 169F241E2016CECE00793E22 /* TreeNodeStructureUITests */, 231 | 169F23F92016CECE00793E22 /* Products */, 232 | ); 233 | sourceTree = ""; 234 | }; 235 | 169F23F92016CECE00793E22 /* Products */ = { 236 | isa = PBXGroup; 237 | children = ( 238 | 169F23F82016CECE00793E22 /* TreeNodeStructure.app */, 239 | 169F24102016CECE00793E22 /* TreeNodeStructureTests.xctest */, 240 | 169F241B2016CECE00793E22 /* TreeNodeStructureUITests.xctest */, 241 | ); 242 | name = Products; 243 | sourceTree = ""; 244 | }; 245 | 169F23FA2016CECE00793E22 /* TreeNodeStructure */ = { 246 | isa = PBXGroup; 247 | children = ( 248 | 169F24432016FD8900793E22 /* Examples */, 249 | 169F242D2016CF5500793E22 /* TreeViewTemplate */, 250 | 169F23FB2016CECE00793E22 /* AppDelegate.h */, 251 | 169F23FC2016CECE00793E22 /* AppDelegate.m */, 252 | 169F23FE2016CECE00793E22 /* ViewController.h */, 253 | 169F23FF2016CECE00793E22 /* ViewController.m */, 254 | 16340A4D20204DDA0026E47F /* AssistMicros.h */, 255 | 169F24012016CECE00793E22 /* Main.storyboard */, 256 | 169F24042016CECE00793E22 /* Assets.xcassets */, 257 | 169F24062016CECE00793E22 /* LaunchScreen.storyboard */, 258 | 169F24092016CECE00793E22 /* Info.plist */, 259 | 169F240A2016CECE00793E22 /* main.m */, 260 | ); 261 | path = TreeNodeStructure; 262 | sourceTree = ""; 263 | }; 264 | 169F24132016CECE00793E22 /* TreeNodeStructureTests */ = { 265 | isa = PBXGroup; 266 | children = ( 267 | 169F24142016CECE00793E22 /* TreeNodeStructureTests.m */, 268 | 169F24162016CECE00793E22 /* Info.plist */, 269 | ); 270 | path = TreeNodeStructureTests; 271 | sourceTree = ""; 272 | }; 273 | 169F241E2016CECE00793E22 /* TreeNodeStructureUITests */ = { 274 | isa = PBXGroup; 275 | children = ( 276 | 169F241F2016CECE00793E22 /* TreeNodeStructureUITests.m */, 277 | 169F24212016CECE00793E22 /* Info.plist */, 278 | ); 279 | path = TreeNodeStructureUITests; 280 | sourceTree = ""; 281 | }; 282 | 169F242D2016CF5500793E22 /* TreeViewTemplate */ = { 283 | isa = PBXGroup; 284 | children = ( 285 | 169F2445201704D400793E22 /* Protocols */, 286 | 169F243C2016FB2600793E22 /* Views */, 287 | 169F243B2016FB1700793E22 /* Nodes */, 288 | ); 289 | path = TreeViewTemplate; 290 | sourceTree = ""; 291 | }; 292 | 169F243B2016FB1700793E22 /* Nodes */ = { 293 | isa = PBXGroup; 294 | children = ( 295 | 169F242E2016CF6800793E22 /* BaseTreeNode.h */, 296 | 169F242F2016CF6800793E22 /* BaseTreeNode.m */, 297 | ); 298 | path = Nodes; 299 | sourceTree = ""; 300 | }; 301 | 169F243C2016FB2600793E22 /* Views */ = { 302 | isa = PBXGroup; 303 | children = ( 304 | 169F24402016FC7000793E22 /* NodeTreeView.h */, 305 | 169F24412016FC7000793E22 /* NodeTreeView.m */, 306 | ); 307 | path = Views; 308 | sourceTree = ""; 309 | }; 310 | 169F24432016FD8900793E22 /* Examples */ = { 311 | isa = PBXGroup; 312 | children = ( 313 | 16340A602021896F0026E47F /* CustomProtocols */, 314 | 16340A4820204B910026E47F /* Controller */, 315 | 16340A4720204B8C0026E47F /* Model */, 316 | 16340A4620204B810026E47F /* View */, 317 | ); 318 | path = Examples; 319 | sourceTree = ""; 320 | }; 321 | 169F2445201704D400793E22 /* Protocols */ = { 322 | isa = PBXGroup; 323 | children = ( 324 | 169F24342016DB7C00793E22 /* NodeModelProtocol.h */, 325 | 169F2444201704CF00793E22 /* NodeViewProtocol.h */, 326 | ); 327 | path = Protocols; 328 | sourceTree = ""; 329 | }; 330 | /* End PBXGroup section */ 331 | 332 | /* Begin PBXNativeTarget section */ 333 | 169F23F72016CECE00793E22 /* TreeNodeStructure */ = { 334 | isa = PBXNativeTarget; 335 | buildConfigurationList = 169F24242016CECE00793E22 /* Build configuration list for PBXNativeTarget "TreeNodeStructure" */; 336 | buildPhases = ( 337 | 169F23F42016CECE00793E22 /* Sources */, 338 | 169F23F52016CECE00793E22 /* Frameworks */, 339 | 169F23F62016CECE00793E22 /* Resources */, 340 | ); 341 | buildRules = ( 342 | ); 343 | dependencies = ( 344 | ); 345 | name = TreeNodeStructure; 346 | productName = TreeNodeStructure; 347 | productReference = 169F23F82016CECE00793E22 /* TreeNodeStructure.app */; 348 | productType = "com.apple.product-type.application"; 349 | }; 350 | 169F240F2016CECE00793E22 /* TreeNodeStructureTests */ = { 351 | isa = PBXNativeTarget; 352 | buildConfigurationList = 169F24272016CECE00793E22 /* Build configuration list for PBXNativeTarget "TreeNodeStructureTests" */; 353 | buildPhases = ( 354 | 169F240C2016CECE00793E22 /* Sources */, 355 | 169F240D2016CECE00793E22 /* Frameworks */, 356 | 169F240E2016CECE00793E22 /* Resources */, 357 | ); 358 | buildRules = ( 359 | ); 360 | dependencies = ( 361 | 169F24122016CECE00793E22 /* PBXTargetDependency */, 362 | ); 363 | name = TreeNodeStructureTests; 364 | productName = TreeNodeStructureTests; 365 | productReference = 169F24102016CECE00793E22 /* TreeNodeStructureTests.xctest */; 366 | productType = "com.apple.product-type.bundle.unit-test"; 367 | }; 368 | 169F241A2016CECE00793E22 /* TreeNodeStructureUITests */ = { 369 | isa = PBXNativeTarget; 370 | buildConfigurationList = 169F242A2016CECE00793E22 /* Build configuration list for PBXNativeTarget "TreeNodeStructureUITests" */; 371 | buildPhases = ( 372 | 169F24172016CECE00793E22 /* Sources */, 373 | 169F24182016CECE00793E22 /* Frameworks */, 374 | 169F24192016CECE00793E22 /* Resources */, 375 | ); 376 | buildRules = ( 377 | ); 378 | dependencies = ( 379 | 169F241D2016CECE00793E22 /* PBXTargetDependency */, 380 | ); 381 | name = TreeNodeStructureUITests; 382 | productName = TreeNodeStructureUITests; 383 | productReference = 169F241B2016CECE00793E22 /* TreeNodeStructureUITests.xctest */; 384 | productType = "com.apple.product-type.bundle.ui-testing"; 385 | }; 386 | /* End PBXNativeTarget section */ 387 | 388 | /* Begin PBXProject section */ 389 | 169F23F02016CECE00793E22 /* Project object */ = { 390 | isa = PBXProject; 391 | attributes = { 392 | LastUpgradeCheck = 0920; 393 | ORGANIZATIONNAME = ccSunday; 394 | TargetAttributes = { 395 | 169F23F72016CECE00793E22 = { 396 | CreatedOnToolsVersion = 9.2; 397 | ProvisioningStyle = Automatic; 398 | }; 399 | 169F240F2016CECE00793E22 = { 400 | CreatedOnToolsVersion = 9.2; 401 | ProvisioningStyle = Automatic; 402 | TestTargetID = 169F23F72016CECE00793E22; 403 | }; 404 | 169F241A2016CECE00793E22 = { 405 | CreatedOnToolsVersion = 9.2; 406 | ProvisioningStyle = Automatic; 407 | TestTargetID = 169F23F72016CECE00793E22; 408 | }; 409 | }; 410 | }; 411 | buildConfigurationList = 169F23F32016CECE00793E22 /* Build configuration list for PBXProject "TreeNodeStructure" */; 412 | compatibilityVersion = "Xcode 8.0"; 413 | developmentRegion = en; 414 | hasScannedForEncodings = 0; 415 | knownRegions = ( 416 | en, 417 | Base, 418 | ); 419 | mainGroup = 169F23EF2016CECE00793E22; 420 | productRefGroup = 169F23F92016CECE00793E22 /* Products */; 421 | projectDirPath = ""; 422 | projectRoot = ""; 423 | targets = ( 424 | 169F23F72016CECE00793E22 /* TreeNodeStructure */, 425 | 169F240F2016CECE00793E22 /* TreeNodeStructureTests */, 426 | 169F241A2016CECE00793E22 /* TreeNodeStructureUITests */, 427 | ); 428 | }; 429 | /* End PBXProject section */ 430 | 431 | /* Begin PBXResourcesBuildPhase section */ 432 | 169F23F62016CECE00793E22 /* Resources */ = { 433 | isa = PBXResourcesBuildPhase; 434 | buildActionMask = 2147483647; 435 | files = ( 436 | 169F24082016CECE00793E22 /* LaunchScreen.storyboard in Resources */, 437 | 169F24052016CECE00793E22 /* Assets.xcassets in Resources */, 438 | 169F24032016CECE00793E22 /* Main.storyboard in Resources */, 439 | ); 440 | runOnlyForDeploymentPostprocessing = 0; 441 | }; 442 | 169F240E2016CECE00793E22 /* Resources */ = { 443 | isa = PBXResourcesBuildPhase; 444 | buildActionMask = 2147483647; 445 | files = ( 446 | ); 447 | runOnlyForDeploymentPostprocessing = 0; 448 | }; 449 | 169F24192016CECE00793E22 /* Resources */ = { 450 | isa = PBXResourcesBuildPhase; 451 | buildActionMask = 2147483647; 452 | files = ( 453 | ); 454 | runOnlyForDeploymentPostprocessing = 0; 455 | }; 456 | /* End PBXResourcesBuildPhase section */ 457 | 458 | /* Begin PBXSourcesBuildPhase section */ 459 | 169F23F42016CECE00793E22 /* Sources */ = { 460 | isa = PBXSourcesBuildPhase; 461 | buildActionMask = 2147483647; 462 | files = ( 463 | 169F244E2017106D00793E22 /* OrganizationNodeView.m in Sources */, 464 | 16340A5C20216C5C0026E47F /* AutoBreadcrumbViewController.m in Sources */, 465 | 169F24002016CECE00793E22 /* ViewController.m in Sources */, 466 | 169F245420173F5500793E22 /* TreeOrganizationDisplayCell.m in Sources */, 467 | 169F240B2016CECE00793E22 /* main.m in Sources */, 468 | 16340A5F20216C7D0026E47F /* AutoExpansionViewController.m in Sources */, 469 | 169F24422016FC7000793E22 /* NodeTreeView.m in Sources */, 470 | 16340A5920216BFE0026E47F /* BaseViewController.m in Sources */, 471 | 16340A4520204B6A0026E47F /* ExpansionViewController.m in Sources */, 472 | 16340A4220204B5F0026E47F /* BreadcrumbViewController.m in Sources */, 473 | 169F2451201736F500793E22 /* BreadcrumbHeaderView.m in Sources */, 474 | 169F24302016CF6800793E22 /* BaseTreeNode.m in Sources */, 475 | 169F23FD2016CECE00793E22 /* AppDelegate.m in Sources */, 476 | 169F243A2016E72000793E22 /* SinglePersonNode.m in Sources */, 477 | 169F24372016E6CE00793E22 /* OrganizationNode.m in Sources */, 478 | 169F24482017064900793E22 /* SinglePersonNodeView.m in Sources */, 479 | ); 480 | runOnlyForDeploymentPostprocessing = 0; 481 | }; 482 | 169F240C2016CECE00793E22 /* Sources */ = { 483 | isa = PBXSourcesBuildPhase; 484 | buildActionMask = 2147483647; 485 | files = ( 486 | 169F24152016CECE00793E22 /* TreeNodeStructureTests.m in Sources */, 487 | ); 488 | runOnlyForDeploymentPostprocessing = 0; 489 | }; 490 | 169F24172016CECE00793E22 /* Sources */ = { 491 | isa = PBXSourcesBuildPhase; 492 | buildActionMask = 2147483647; 493 | files = ( 494 | 169F24202016CECE00793E22 /* TreeNodeStructureUITests.m in Sources */, 495 | ); 496 | runOnlyForDeploymentPostprocessing = 0; 497 | }; 498 | /* End PBXSourcesBuildPhase section */ 499 | 500 | /* Begin PBXTargetDependency section */ 501 | 169F24122016CECE00793E22 /* PBXTargetDependency */ = { 502 | isa = PBXTargetDependency; 503 | target = 169F23F72016CECE00793E22 /* TreeNodeStructure */; 504 | targetProxy = 169F24112016CECE00793E22 /* PBXContainerItemProxy */; 505 | }; 506 | 169F241D2016CECE00793E22 /* PBXTargetDependency */ = { 507 | isa = PBXTargetDependency; 508 | target = 169F23F72016CECE00793E22 /* TreeNodeStructure */; 509 | targetProxy = 169F241C2016CECE00793E22 /* PBXContainerItemProxy */; 510 | }; 511 | /* End PBXTargetDependency section */ 512 | 513 | /* Begin PBXVariantGroup section */ 514 | 169F24012016CECE00793E22 /* Main.storyboard */ = { 515 | isa = PBXVariantGroup; 516 | children = ( 517 | 169F24022016CECE00793E22 /* Base */, 518 | ); 519 | name = Main.storyboard; 520 | sourceTree = ""; 521 | }; 522 | 169F24062016CECE00793E22 /* LaunchScreen.storyboard */ = { 523 | isa = PBXVariantGroup; 524 | children = ( 525 | 169F24072016CECE00793E22 /* Base */, 526 | ); 527 | name = LaunchScreen.storyboard; 528 | sourceTree = ""; 529 | }; 530 | /* End PBXVariantGroup section */ 531 | 532 | /* Begin XCBuildConfiguration section */ 533 | 169F24222016CECE00793E22 /* Debug */ = { 534 | isa = XCBuildConfiguration; 535 | buildSettings = { 536 | ALWAYS_SEARCH_USER_PATHS = NO; 537 | CLANG_ANALYZER_NONNULL = YES; 538 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 539 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 540 | CLANG_CXX_LIBRARY = "libc++"; 541 | CLANG_ENABLE_MODULES = YES; 542 | CLANG_ENABLE_OBJC_ARC = YES; 543 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 544 | CLANG_WARN_BOOL_CONVERSION = YES; 545 | CLANG_WARN_COMMA = YES; 546 | CLANG_WARN_CONSTANT_CONVERSION = YES; 547 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 548 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 549 | CLANG_WARN_EMPTY_BODY = YES; 550 | CLANG_WARN_ENUM_CONVERSION = YES; 551 | CLANG_WARN_INFINITE_RECURSION = YES; 552 | CLANG_WARN_INT_CONVERSION = YES; 553 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 554 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 555 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 556 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 557 | CLANG_WARN_STRICT_PROTOTYPES = YES; 558 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 559 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 560 | CLANG_WARN_UNREACHABLE_CODE = YES; 561 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 562 | CODE_SIGN_IDENTITY = "iPhone Developer"; 563 | COPY_PHASE_STRIP = NO; 564 | DEBUG_INFORMATION_FORMAT = dwarf; 565 | ENABLE_STRICT_OBJC_MSGSEND = YES; 566 | ENABLE_TESTABILITY = YES; 567 | GCC_C_LANGUAGE_STANDARD = gnu11; 568 | GCC_DYNAMIC_NO_PIC = NO; 569 | GCC_NO_COMMON_BLOCKS = YES; 570 | GCC_OPTIMIZATION_LEVEL = 0; 571 | GCC_PREPROCESSOR_DEFINITIONS = ( 572 | "DEBUG=1", 573 | "$(inherited)", 574 | ); 575 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 576 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 577 | GCC_WARN_UNDECLARED_SELECTOR = YES; 578 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 579 | GCC_WARN_UNUSED_FUNCTION = YES; 580 | GCC_WARN_UNUSED_VARIABLE = YES; 581 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 582 | MTL_ENABLE_DEBUG_INFO = YES; 583 | ONLY_ACTIVE_ARCH = YES; 584 | SDKROOT = iphoneos; 585 | }; 586 | name = Debug; 587 | }; 588 | 169F24232016CECE00793E22 /* Release */ = { 589 | isa = XCBuildConfiguration; 590 | buildSettings = { 591 | ALWAYS_SEARCH_USER_PATHS = NO; 592 | CLANG_ANALYZER_NONNULL = YES; 593 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 594 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 595 | CLANG_CXX_LIBRARY = "libc++"; 596 | CLANG_ENABLE_MODULES = YES; 597 | CLANG_ENABLE_OBJC_ARC = YES; 598 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 599 | CLANG_WARN_BOOL_CONVERSION = YES; 600 | CLANG_WARN_COMMA = YES; 601 | CLANG_WARN_CONSTANT_CONVERSION = YES; 602 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 603 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 604 | CLANG_WARN_EMPTY_BODY = YES; 605 | CLANG_WARN_ENUM_CONVERSION = YES; 606 | CLANG_WARN_INFINITE_RECURSION = YES; 607 | CLANG_WARN_INT_CONVERSION = YES; 608 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 609 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 610 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 611 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 612 | CLANG_WARN_STRICT_PROTOTYPES = YES; 613 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 614 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 615 | CLANG_WARN_UNREACHABLE_CODE = YES; 616 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 617 | CODE_SIGN_IDENTITY = "iPhone Developer"; 618 | COPY_PHASE_STRIP = NO; 619 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 620 | ENABLE_NS_ASSERTIONS = NO; 621 | ENABLE_STRICT_OBJC_MSGSEND = YES; 622 | GCC_C_LANGUAGE_STANDARD = gnu11; 623 | GCC_NO_COMMON_BLOCKS = YES; 624 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 625 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 626 | GCC_WARN_UNDECLARED_SELECTOR = YES; 627 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 628 | GCC_WARN_UNUSED_FUNCTION = YES; 629 | GCC_WARN_UNUSED_VARIABLE = YES; 630 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 631 | MTL_ENABLE_DEBUG_INFO = NO; 632 | SDKROOT = iphoneos; 633 | VALIDATE_PRODUCT = YES; 634 | }; 635 | name = Release; 636 | }; 637 | 169F24252016CECE00793E22 /* Debug */ = { 638 | isa = XCBuildConfiguration; 639 | buildSettings = { 640 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 641 | CODE_SIGN_STYLE = Automatic; 642 | DEVELOPMENT_TEAM = PE6PGUS8E7; 643 | INFOPLIST_FILE = TreeNodeStructure/Info.plist; 644 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 645 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 646 | PRODUCT_BUNDLE_IDENTIFIER = com.lionbridge.help.TreeNodeStructure; 647 | PRODUCT_NAME = "$(TARGET_NAME)"; 648 | TARGETED_DEVICE_FAMILY = "1,2"; 649 | }; 650 | name = Debug; 651 | }; 652 | 169F24262016CECE00793E22 /* Release */ = { 653 | isa = XCBuildConfiguration; 654 | buildSettings = { 655 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 656 | CODE_SIGN_STYLE = Automatic; 657 | DEVELOPMENT_TEAM = PE6PGUS8E7; 658 | INFOPLIST_FILE = TreeNodeStructure/Info.plist; 659 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 660 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 661 | PRODUCT_BUNDLE_IDENTIFIER = com.lionbridge.help.TreeNodeStructure; 662 | PRODUCT_NAME = "$(TARGET_NAME)"; 663 | TARGETED_DEVICE_FAMILY = "1,2"; 664 | }; 665 | name = Release; 666 | }; 667 | 169F24282016CECE00793E22 /* Debug */ = { 668 | isa = XCBuildConfiguration; 669 | buildSettings = { 670 | BUNDLE_LOADER = "$(TEST_HOST)"; 671 | CODE_SIGN_STYLE = Automatic; 672 | INFOPLIST_FILE = TreeNodeStructureTests/Info.plist; 673 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 674 | PRODUCT_BUNDLE_IDENTIFIER = com.lionbridge.help.TreeNodeStructureTests; 675 | PRODUCT_NAME = "$(TARGET_NAME)"; 676 | TARGETED_DEVICE_FAMILY = "1,2"; 677 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TreeNodeStructure.app/TreeNodeStructure"; 678 | }; 679 | name = Debug; 680 | }; 681 | 169F24292016CECE00793E22 /* Release */ = { 682 | isa = XCBuildConfiguration; 683 | buildSettings = { 684 | BUNDLE_LOADER = "$(TEST_HOST)"; 685 | CODE_SIGN_STYLE = Automatic; 686 | INFOPLIST_FILE = TreeNodeStructureTests/Info.plist; 687 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 688 | PRODUCT_BUNDLE_IDENTIFIER = com.lionbridge.help.TreeNodeStructureTests; 689 | PRODUCT_NAME = "$(TARGET_NAME)"; 690 | TARGETED_DEVICE_FAMILY = "1,2"; 691 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TreeNodeStructure.app/TreeNodeStructure"; 692 | }; 693 | name = Release; 694 | }; 695 | 169F242B2016CECE00793E22 /* Debug */ = { 696 | isa = XCBuildConfiguration; 697 | buildSettings = { 698 | CODE_SIGN_STYLE = Automatic; 699 | INFOPLIST_FILE = TreeNodeStructureUITests/Info.plist; 700 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 701 | PRODUCT_BUNDLE_IDENTIFIER = com.lionbridge.help.TreeNodeStructureUITests; 702 | PRODUCT_NAME = "$(TARGET_NAME)"; 703 | TARGETED_DEVICE_FAMILY = "1,2"; 704 | TEST_TARGET_NAME = TreeNodeStructure; 705 | }; 706 | name = Debug; 707 | }; 708 | 169F242C2016CECE00793E22 /* Release */ = { 709 | isa = XCBuildConfiguration; 710 | buildSettings = { 711 | CODE_SIGN_STYLE = Automatic; 712 | INFOPLIST_FILE = TreeNodeStructureUITests/Info.plist; 713 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 714 | PRODUCT_BUNDLE_IDENTIFIER = com.lionbridge.help.TreeNodeStructureUITests; 715 | PRODUCT_NAME = "$(TARGET_NAME)"; 716 | TARGETED_DEVICE_FAMILY = "1,2"; 717 | TEST_TARGET_NAME = TreeNodeStructure; 718 | }; 719 | name = Release; 720 | }; 721 | /* End XCBuildConfiguration section */ 722 | 723 | /* Begin XCConfigurationList section */ 724 | 169F23F32016CECE00793E22 /* Build configuration list for PBXProject "TreeNodeStructure" */ = { 725 | isa = XCConfigurationList; 726 | buildConfigurations = ( 727 | 169F24222016CECE00793E22 /* Debug */, 728 | 169F24232016CECE00793E22 /* Release */, 729 | ); 730 | defaultConfigurationIsVisible = 0; 731 | defaultConfigurationName = Release; 732 | }; 733 | 169F24242016CECE00793E22 /* Build configuration list for PBXNativeTarget "TreeNodeStructure" */ = { 734 | isa = XCConfigurationList; 735 | buildConfigurations = ( 736 | 169F24252016CECE00793E22 /* Debug */, 737 | 169F24262016CECE00793E22 /* Release */, 738 | ); 739 | defaultConfigurationIsVisible = 0; 740 | defaultConfigurationName = Release; 741 | }; 742 | 169F24272016CECE00793E22 /* Build configuration list for PBXNativeTarget "TreeNodeStructureTests" */ = { 743 | isa = XCConfigurationList; 744 | buildConfigurations = ( 745 | 169F24282016CECE00793E22 /* Debug */, 746 | 169F24292016CECE00793E22 /* Release */, 747 | ); 748 | defaultConfigurationIsVisible = 0; 749 | defaultConfigurationName = Release; 750 | }; 751 | 169F242A2016CECE00793E22 /* Build configuration list for PBXNativeTarget "TreeNodeStructureUITests" */ = { 752 | isa = XCConfigurationList; 753 | buildConfigurations = ( 754 | 169F242B2016CECE00793E22 /* Debug */, 755 | 169F242C2016CECE00793E22 /* Release */, 756 | ); 757 | defaultConfigurationIsVisible = 0; 758 | defaultConfigurationName = Release; 759 | }; 760 | /* End XCConfigurationList section */ 761 | }; 762 | rootObject = 169F23F02016CECE00793E22 /* Project object */; 763 | } 764 | -------------------------------------------------------------------------------- /TreeNodeStructure.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TreeNodeStructure.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TreeNodeStructure.xcodeproj/xcuserdata/ccsunday.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TreeNodeStructure.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /TreeNodeStructure/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /TreeNodeStructure/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /TreeNodeStructure/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /TreeNodeStructure/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TreeNodeStructure/Assets.xcassets/next.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "d_next@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "d_next@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TreeNodeStructure/Assets.xcassets/next.imageset/d_next@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunday1990/TreeViewTemplate/bfd578748c42a60bbfaa86e60df92be9e67fc63d/TreeNodeStructure/Assets.xcassets/next.imageset/d_next@2x.png -------------------------------------------------------------------------------- /TreeNodeStructure/Assets.xcassets/next.imageset/d_next@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunday1990/TreeViewTemplate/bfd578748c42a60bbfaa86e60df92be9e67fc63d/TreeNodeStructure/Assets.xcassets/next.imageset/d_next@3x.png -------------------------------------------------------------------------------- /TreeNodeStructure/Assets.xcassets/node_normal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "jiaqian_xuanzhe_icon_@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "jiaqian_xuanzhe_icon_@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TreeNodeStructure/Assets.xcassets/node_normal.imageset/jiaqian_xuanzhe_icon_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunday1990/TreeViewTemplate/bfd578748c42a60bbfaa86e60df92be9e67fc63d/TreeNodeStructure/Assets.xcassets/node_normal.imageset/jiaqian_xuanzhe_icon_@2x.png -------------------------------------------------------------------------------- /TreeNodeStructure/Assets.xcassets/node_normal.imageset/jiaqian_xuanzhe_icon_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunday1990/TreeViewTemplate/bfd578748c42a60bbfaa86e60df92be9e67fc63d/TreeNodeStructure/Assets.xcassets/node_normal.imageset/jiaqian_xuanzhe_icon_@3x.png -------------------------------------------------------------------------------- /TreeNodeStructure/Assets.xcassets/node_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "jiaqian_xuanzhong_icon_@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "jiaqian_xuanzhong_icon_@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TreeNodeStructure/Assets.xcassets/node_selected.imageset/jiaqian_xuanzhong_icon_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunday1990/TreeViewTemplate/bfd578748c42a60bbfaa86e60df92be9e67fc63d/TreeNodeStructure/Assets.xcassets/node_selected.imageset/jiaqian_xuanzhong_icon_@2x.png -------------------------------------------------------------------------------- /TreeNodeStructure/Assets.xcassets/node_selected.imageset/jiaqian_xuanzhong_icon_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunday1990/TreeViewTemplate/bfd578748c42a60bbfaa86e60df92be9e67fc63d/TreeNodeStructure/Assets.xcassets/node_selected.imageset/jiaqian_xuanzhong_icon_@3x.png -------------------------------------------------------------------------------- /TreeNodeStructure/AssistMicros.h: -------------------------------------------------------------------------------- 1 | // 2 | // AssistMicros.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/30. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #ifndef AssistMicros_h 10 | #define AssistMicros_h 11 | 12 | #define HEIGHT [UIScreen mainScreen].bounds.size.height 13 | 14 | #define WIDTH [UIScreen mainScreen].bounds.size.width 15 | 16 | 17 | #endif /* AssistMicros_h */ 18 | -------------------------------------------------------------------------------- /TreeNodeStructure/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /TreeNodeStructure/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/Controller/AutoRefresh/AutoBreadcrumbViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AutoBreadcrumbViewController.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/31. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BaseViewController.h" 11 | 12 | @interface AutoBreadcrumbViewController : BaseViewController 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/Controller/AutoRefresh/AutoBreadcrumbViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AutoBreadcrumbViewController.m 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/31. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import "AutoBreadcrumbViewController.h" 10 | #import "BreadcrumbHeaderView.h" 11 | 12 | @interface AutoBreadcrumbViewController () 13 | /** 14 | 面包屑 15 | */ 16 | @property (nonatomic, strong) BreadcrumbHeaderView *breadcrumbView; 17 | 18 | @end 19 | 20 | @implementation AutoBreadcrumbViewController 21 | #pragma mark ======== Life Cycle ======== 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | // Do any additional setup after loading the view. 25 | } 26 | 27 | - (void)didReceiveMemoryWarning { 28 | [super didReceiveMemoryWarning]; 29 | // Dispose of any resources that can be recreated. 30 | } 31 | 32 | #pragma mark ======== NetWork ======== 33 | 34 | #pragma mark ======== System Delegate ======== 35 | 36 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ 37 | return self.currentNode.subTreeHeight; 38 | } 39 | 40 | - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{ 41 | return 44; 42 | } 43 | 44 | - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{ 45 | return 12; 46 | } 47 | 48 | #pragma mark UITableViewDelegate 49 | 50 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ 51 | static NSString *CELL_ID = @"StructureTreeOrganizationDisplayCellID"; 52 | TreeOrganizationDisplayCell *cell = [tableView dequeueReusableCellWithIdentifier:CELL_ID]; 53 | if (cell == nil) { 54 | cell = [[TreeOrganizationDisplayCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CELL_ID treeStyle:NodeTreeViewStyleBreadcrumbs treeRefreshPolicy:NodeTreeRefreshPolicyAutomic]; 55 | //cell事件的block回调,只负责将所选择的点传递出来,更新headerview,不需要手动刷新 56 | __weak typeof(self)weakSelf = self; 57 | cell.selectNode = ^(BaseTreeNode *node) { 58 | if (node.subNodes.count > 0) { 59 | [weakSelf selectNode:node nodeTreeAnimation:weakSelf.rowAnimation]; 60 | } 61 | }; 62 | } 63 | [cell reloadTreeViewWithNode:self.currentNode RowAnimation:self.rowAnimation]; 64 | return cell; 65 | } 66 | 67 | - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{ 68 | return self.breadcrumbView; 69 | } 70 | 71 | #pragma mark ======== Custom Delegate ======== 72 | 73 | #pragma mark ======== Notifications && Observers ======== 74 | 75 | #pragma mark ======== Method Overrides ======== 76 | - (void)selectNode:(BaseTreeNode *)node nodeTreeAnimation:(UITableViewRowAnimation)rowAnimation{ 77 | //更新header 78 | if (node.subNodes.count>0) { 79 | if ([node isMemberOfClass:[SinglePersonNode class]]) { 80 | SinglePersonNode *personNode = (SinglePersonNode *)node; 81 | [self.breadcrumbView addSelectedNode:personNode withTitle:personNode.name]; 82 | }else if ([node isMemberOfClass:[OrganizationNode class]]){ 83 | OrganizationNode *orgNode = (OrganizationNode *)node; 84 | [self.breadcrumbView addSelectedNode:orgNode withTitle:orgNode.title]; 85 | }else{ 86 | [self.breadcrumbView addSelectedNode:node withTitle:@"xxx公司"]; 87 | } 88 | } 89 | } 90 | 91 | #pragma mark ======== Event Response ======== 92 | 93 | #pragma mark ======== Private Methods ======== 94 | 95 | #pragma mark ======== Setters && Getters ======== 96 | - (BreadcrumbHeaderView *)breadcrumbView{ 97 | if (!_breadcrumbView) { 98 | _breadcrumbView = [[BreadcrumbHeaderView alloc]initWithFrame:CGRectMake(0, 0, WIDTH, 44)]; 99 | _breadcrumbView.alwaysBounceVertical = NO; 100 | _breadcrumbView.bounces = YES; 101 | _breadcrumbView.showsHorizontalScrollIndicator = YES; 102 | _breadcrumbView.backgroundColor = [UIColor colorWithRed:236/255.0 green:236/255.0 blue:236/255.0 alpha:1.0]; 103 | __weak typeof(self)weakSelf = self; 104 | [_breadcrumbView addSelectedNode:weakSelf.currentNode withTitle:@"xxx公司"]; 105 | _breadcrumbView.selectNode = ^(BaseTreeNode *node,UITableViewRowAnimation nodeTreeAnimation) { 106 | if (node.subNodes.count == 0) { 107 | NSLog(@"do nothing"); 108 | }else{ 109 | weakSelf.currentNode = node; 110 | weakSelf.rowAnimation = UITableViewRowAnimationRight; 111 | [weakSelf selectNode:node nodeTreeAnimation:weakSelf.rowAnimation]; 112 | [weakSelf.tableview reloadData]; 113 | } 114 | }; 115 | } 116 | return _breadcrumbView; 117 | } 118 | /* 119 | #pragma mark - Navigation 120 | 121 | // In a storyboard-based application, you will often want to do a little preparation before navigation 122 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 123 | // Get the new view controller using [segue destinationViewController]. 124 | // Pass the selected object to the new view controller. 125 | } 126 | */ 127 | 128 | @end 129 | 130 | 131 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/Controller/AutoRefresh/AutoExpansionViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AutoExpansionViewController.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/31. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BaseViewController.h" 11 | 12 | @interface AutoExpansionViewController : BaseViewController 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/Controller/AutoRefresh/AutoExpansionViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AutoExpansionViewController.m 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/31. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import "AutoExpansionViewController.h" 10 | 11 | @interface AutoExpansionViewController () 12 | 13 | @end 14 | 15 | @implementation AutoExpansionViewController 16 | #pragma mark ======== Life Cycle ======== 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view. 20 | } 21 | 22 | - (void)didReceiveMemoryWarning { 23 | [super didReceiveMemoryWarning]; 24 | // Dispose of any resources that can be recreated. 25 | } 26 | 27 | #pragma mark ======== NetWork ======== 28 | 29 | #pragma mark ======== System Delegate ======== 30 | 31 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ 32 | static NSString *CELL_ID = @"StructureTreeOrganizationDisplayCellID"; 33 | TreeOrganizationDisplayCell *cell = [tableView dequeueReusableCellWithIdentifier:CELL_ID]; 34 | if (cell == nil) { 35 | cell = [[TreeOrganizationDisplayCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CELL_ID treeStyle:NodeTreeViewStyleExpansion treeRefreshPolicy:NodeTreeRefreshPolicyAutomic]; 36 | __weak typeof(self)weakSelf = self; 37 | cell.selectNode = ^(BaseTreeNode *node) { 38 | [weakSelf.tableview reloadData]; 39 | }; 40 | } 41 | [cell reloadTreeViewWithNode:self.currentNode RowAnimation:UITableViewRowAnimationFade]; 42 | 43 | return cell; 44 | } 45 | 46 | #pragma mark ======== Custom Delegate ======== 47 | 48 | #pragma mark ======== Notifications && Observers ======== 49 | 50 | #pragma mark ======== Event Response ======== 51 | 52 | #pragma mark ======== Private Methods ======== 53 | 54 | #pragma mark ======== Setters && Getters ======== 55 | 56 | /* 57 | #pragma mark - Navigation 58 | 59 | // In a storyboard-based application, you will often want to do a little preparation before navigation 60 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 61 | // Get the new view controller using [segue destinationViewController]. 62 | // Pass the selected object to the new view controller. 63 | } 64 | */ 65 | 66 | @end 67 | 68 | 69 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/Controller/Base/BaseViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewController.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/30. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AssistMicros.h" 11 | 12 | #import "OrganizationNode.h" 13 | #import "SinglePersonNode.h" 14 | 15 | #import "TreeOrganizationDisplayCell.h" 16 | 17 | @interface BaseViewController : UIViewController 18 | { 19 | BaseTreeNode *_baseNode; 20 | } 21 | /** 22 | tableView 23 | */ 24 | @property (nonatomic, strong) UITableView *tableview; 25 | /** 26 | 当前展示的node 27 | */ 28 | @property (nonatomic, strong) BaseTreeNode *currentNode; 29 | /** 30 | tableview展开方式 31 | */ 32 | @property (nonatomic, assign) UITableViewRowAnimation rowAnimation; 33 | 34 | - (void)selectNode:(BaseTreeNode *)node nodeTreeAnimation:(UITableViewRowAnimation)rowAnimation; 35 | 36 | @end 37 | 38 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/Controller/Base/BaseViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewController.m 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/30. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import "BaseViewController.h" 10 | 11 | @interface BaseViewController () 12 | 13 | @property (nonatomic, strong) UIButton *dismissBtn; 14 | 15 | @end 16 | 17 | @implementation BaseViewController 18 | #pragma mark ======== Life Cycle ======== 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | // Do any additional setup after loading the view. 22 | self.rowAnimation = UITableViewRowAnimationNone; 23 | self.view.backgroundColor = [UIColor whiteColor]; 24 | [self.view addSubview:self.tableview]; 25 | [self.view addSubview:self.dismissBtn]; 26 | [self loadData]; 27 | } 28 | 29 | - (void)didReceiveMemoryWarning { 30 | [super didReceiveMemoryWarning]; 31 | // Dispose of any resources that can be recreated. 32 | } 33 | 34 | #pragma mark ======== NetWork ======== 35 | 36 | #pragma mark ======== System Delegate ======== 37 | 38 | #pragma mark ======== Custom Delegate ======== 39 | #pragma mark UITableViewDataSource 40 | 41 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ 42 | return 1; 43 | } 44 | 45 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ 46 | return self.currentNode?1:0; 47 | } 48 | 49 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ 50 | return self.currentNode.currentTreeHeight; 51 | } 52 | 53 | #pragma mark UITableViewDelegate 54 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ 55 | static NSString *CELL_ID = @"StructureTreeOrganizationDisplayCellID"; 56 | TreeOrganizationDisplayCell *cell = [tableView dequeueReusableCellWithIdentifier:CELL_ID]; 57 | if (cell == nil) { 58 | cell = [[TreeOrganizationDisplayCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CELL_ID treeStyle:NodeTreeViewStyleExpansion]; 59 | //cell事件的block回调 60 | __weak typeof(self)weakSelf = self; 61 | cell.selectNode = ^(BaseTreeNode *node) { 62 | [weakSelf selectNode:node nodeTreeAnimation:UITableViewRowAnimationNone]; 63 | }; 64 | } 65 | [cell reloadTreeViewWithNode:self.currentNode RowAnimation:UITableViewRowAnimationNone]; 66 | return cell; 67 | } 68 | 69 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView{ 70 | [self.view endEditing:YES]; 71 | } 72 | 73 | #pragma mark ======== Notifications && Observers ======== 74 | 75 | #pragma mark ======== Event Response ======== 76 | - (void)dismiss{ 77 | [self dismissViewControllerAnimated:YES completion:nil]; 78 | } 79 | #pragma mark ======== Private Methods ======== 80 | - (void)loadData{ 81 | //数据处理 82 | BaseTreeNode *baseNode = [[BaseTreeNode alloc]init]; 83 | baseNode.fatherNode = baseNode;//父节点等于自身 84 | _baseNode = baseNode; 85 | for (int i = 0; i<10; i++) { 86 | if (i<8) { 87 | OrganizationNode *simpleNode = [[OrganizationNode alloc]init]; 88 | simpleNode.title = [NSString stringWithFormat:@"部门%d",i]; 89 | for (int j = 0; j<5; j++) { 90 | OrganizationNode *personNode = [[OrganizationNode alloc]init]; 91 | personNode.nodeHeight = 60; 92 | personNode.title = [NSString stringWithFormat:@"%@的分部门%d",simpleNode.title,j]; 93 | for (int k = 0; k<6; k++) { 94 | OrganizationNode *personNode0 = [[OrganizationNode alloc]init]; 95 | personNode0.title = [NSString stringWithFormat:@"分部门%d的人员%d",j,k]; 96 | personNode0.nodeHeight = 80; 97 | for (int m = 0; m<7; m++) { 98 | SinglePersonNode *personNode1 = [[SinglePersonNode alloc]init]; 99 | personNode1.nodeHeight = 60; 100 | personNode1.name = [NSString stringWithFormat:@"%@-张三%d",personNode.title,m]; 101 | personNode1.IDNum =@"1003022"; 102 | personNode1.dePartment =@"资金部"; 103 | [personNode0 addSubNode:personNode1]; 104 | } 105 | [personNode addSubNode:personNode0]; 106 | } 107 | [simpleNode addSubNode:personNode]; 108 | } 109 | [baseNode addSubNode:simpleNode]; 110 | }else{ 111 | SinglePersonNode *singlePersonNode1 = [[SinglePersonNode alloc]init]; 112 | singlePersonNode1.nodeHeight = 60; 113 | singlePersonNode1.name = [NSString stringWithFormat:@"张三%d",i]; 114 | singlePersonNode1.IDNum =@"1003022"; 115 | [baseNode addSubNode:singlePersonNode1]; 116 | } 117 | } 118 | self.currentNode = baseNode; 119 | [self.tableview reloadData]; 120 | } 121 | 122 | - (void)selectNode:(BaseTreeNode *)node nodeTreeAnimation:(UITableViewRowAnimation)rowAnimation{ 123 | self.currentNode = node; 124 | [self.tableview reloadData]; 125 | } 126 | 127 | #pragma mark ======== Setters && Getters ======== 128 | - (UITableView *)tableview{ 129 | if (!_tableview) { 130 | _tableview = [[UITableView alloc]initWithFrame:CGRectMake(0, 64, WIDTH, HEIGHT-64) style:UITableViewStylePlain]; 131 | _tableview.tableFooterView = [[UIView alloc]init]; 132 | _tableview.delegate = self; 133 | _tableview.dataSource = self; 134 | } 135 | return _tableview; 136 | } 137 | 138 | - (UIButton *)dismissBtn{ 139 | if (!_dismissBtn) { 140 | _dismissBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 141 | _dismissBtn.frame = CGRectMake(6,12 , 44, 40); 142 | _dismissBtn.contentMode = UIViewContentModeLeft; 143 | [_dismissBtn setTitle:@"返回" forState:UIControlStateNormal]; 144 | [_dismissBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 145 | [_dismissBtn addTarget:self action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside]; 146 | } 147 | return _dismissBtn; 148 | } 149 | /* 150 | #pragma mark - Navigation 151 | 152 | // In a storyboard-based application, you will often want to do a little preparation before navigation 153 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 154 | // Get the new view controller using [segue destinationViewController]. 155 | // Pass the selected object to the new view controller. 156 | } 157 | */ 158 | 159 | @end 160 | 161 | 162 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/Controller/ManaulRefresh/BreadcrumbViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // BreadcrumbViewController.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/30. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BaseViewController.h" 11 | @interface BreadcrumbViewController : BaseViewController 12 | @end 13 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/Controller/ManaulRefresh/BreadcrumbViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // BreadcrumbViewController.m 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/30. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import "BreadcrumbViewController.h" 10 | #import "BreadcrumbHeaderView.h" 11 | 12 | @interface BreadcrumbViewController () 13 | /** 14 | 面包屑 15 | */ 16 | @property (nonatomic, strong) BreadcrumbHeaderView *breadcrumbView; 17 | @end 18 | 19 | @implementation BreadcrumbViewController 20 | #pragma mark ======== Life Cycle ======== 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | // Do any additional setup after loading the view. 24 | } 25 | 26 | - (void)didReceiveMemoryWarning { 27 | [super didReceiveMemoryWarning]; 28 | // Dispose of any resources that can be recreated. 29 | } 30 | 31 | #pragma mark ======== NetWork ======== 32 | 33 | #pragma mark ======== System Delegate ======== 34 | 35 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ 36 | return self.currentNode.subTreeHeight; 37 | } 38 | 39 | - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{ 40 | return 44; 41 | } 42 | 43 | - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{ 44 | return 12; 45 | } 46 | 47 | #pragma mark UITableViewDelegate 48 | 49 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ 50 | static NSString *CELL_ID = @"StructureTreeOrganizationDisplayCellID"; 51 | TreeOrganizationDisplayCell *cell = [tableView dequeueReusableCellWithIdentifier:CELL_ID]; 52 | if (cell == nil) { 53 | cell = [[TreeOrganizationDisplayCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CELL_ID treeStyle:NodeTreeViewStyleBreadcrumbs]; 54 | //cell事件的block回调 55 | __weak typeof(self)weakSelf = self; 56 | cell.selectNode = ^(BaseTreeNode *node) { 57 | #warning 此处可以模拟网络获取节点进行展示 58 | [weakSelf selectNode:node nodeTreeAnimation:UITableViewRowAnimationFade]; 59 | }; 60 | } 61 | [cell reloadTreeViewWithNode:self.currentNode RowAnimation:self.rowAnimation]; 62 | return cell; 63 | } 64 | 65 | - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{ 66 | return self.breadcrumbView; 67 | } 68 | 69 | #pragma mark ======== Custom Delegate ======== 70 | 71 | #pragma mark ======== Notifications && Observers ======== 72 | 73 | #pragma mark ======== Method Overrides ======== 74 | 75 | - (void)selectNode:(BaseTreeNode *)node nodeTreeAnimation:(UITableViewRowAnimation)rowAnimation{ 76 | self.rowAnimation = rowAnimation; 77 | //更新header 78 | if (node.subNodes.count>0) { 79 | self.currentNode = node; 80 | if ([node isMemberOfClass:[SinglePersonNode class]]) { 81 | SinglePersonNode *cusNode = (SinglePersonNode *)node; 82 | [self.breadcrumbView addSelectedNode:node withTitle:cusNode.name]; 83 | }else if ([node isMemberOfClass:[OrganizationNode class]]){ 84 | OrganizationNode *simpleNode = (OrganizationNode *)node; 85 | [self.breadcrumbView addSelectedNode:simpleNode withTitle:simpleNode.title]; 86 | }else{ 87 | [self.breadcrumbView addSelectedNode:node withTitle:@"xxx公司"]; 88 | } 89 | } 90 | [self.tableview reloadData]; 91 | } 92 | 93 | #pragma mark ======== Event Response ======== 94 | 95 | #pragma mark ======== Private Methods ======== 96 | 97 | #pragma mark ======== Setters && Getters ======== 98 | - (BreadcrumbHeaderView *)breadcrumbView{ 99 | if (!_breadcrumbView) { 100 | _breadcrumbView = [[BreadcrumbHeaderView alloc]initWithFrame:CGRectMake(0, 0, WIDTH, 44)]; 101 | _breadcrumbView.alwaysBounceVertical = NO; 102 | _breadcrumbView.bounces = YES; 103 | _breadcrumbView.showsHorizontalScrollIndicator = YES; 104 | _breadcrumbView.backgroundColor = [UIColor colorWithRed:236/255.0 green:236/255.0 blue:236/255.0 alpha:1.0]; 105 | __weak typeof(self)weakSelf = self; 106 | [_breadcrumbView addSelectedNode:weakSelf.currentNode withTitle:@"xxx公司"]; 107 | _breadcrumbView.selectNode = ^(BaseTreeNode *node,UITableViewRowAnimation nodeTreeAnimation) { 108 | if (node.subNodes.count == 0) { 109 | NSLog(@"do nothing"); 110 | }else{ 111 | [weakSelf selectNode:node nodeTreeAnimation:nodeTreeAnimation]; 112 | } 113 | }; 114 | } 115 | return _breadcrumbView; 116 | } 117 | 118 | /* 119 | #pragma mark - Navigation 120 | 121 | // In a storyboard-based application, you will often want to do a little preparation before navigation 122 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 123 | // Get the new view controller using [segue destinationViewController]. 124 | // Pass the selected object to the new view controller. 125 | } 126 | */ 127 | 128 | @end 129 | 130 | 131 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/Controller/ManaulRefresh/ExpansionViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ExpansionViewController.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/30. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BaseViewController.h" 11 | @interface ExpansionViewController : BaseViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/Controller/ManaulRefresh/ExpansionViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ExpansionViewController.m 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/30. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import "ExpansionViewController.h" 10 | #import "AssistMicros.h" 11 | 12 | #import "OrganizationNode.h" 13 | #import "SinglePersonNode.h" 14 | 15 | #import "TreeOrganizationDisplayCell.h" 16 | 17 | @interface ExpansionViewController () 18 | 19 | @end 20 | 21 | @implementation ExpansionViewController 22 | #pragma mark ======== Life Cycle ======== 23 | - (void)viewDidLoad { 24 | [super viewDidLoad]; 25 | // Do any additional setup after loading the view. 26 | } 27 | 28 | - (void)didReceiveMemoryWarning { 29 | [super didReceiveMemoryWarning]; 30 | // Dispose of any resources that can be recreated. 31 | } 32 | 33 | #pragma mark ======== NetWork ======== 34 | 35 | #pragma mark ======== System Delegate ======== 36 | //-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ 37 | // return [super tableView:tableView heightForRowAtIndexPath:indexPath]; 38 | //} 39 | #pragma mark ======== Custom Delegate ======== 40 | 41 | #pragma mark ======== Notifications && Observers ======== 42 | 43 | #pragma mark ======== Method Overrides ======== 44 | //- (void)selectNode:(BaseTreeNode *)node nodeTreeAnimation:(UITableViewRowAnimation)rowAnimation{ 45 | // [super selectNode:node nodeTreeAnimation:rowAnimation]; 46 | //} 47 | 48 | #pragma mark ======== Event Response ======== 49 | 50 | #pragma mark ======== Private Methods ======== 51 | 52 | #pragma mark ======== Setters && Getters ======== 53 | /* 54 | #pragma mark - Navigation 55 | 56 | // In a storyboard-based application, you will often want to do a little preparation before navigation 57 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 58 | // Get the new view controller using [segue destinationViewController]. 59 | // Pass the selected object to the new view controller. 60 | } 61 | */ 62 | 63 | @end 64 | 65 | 66 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/CustomProtocols/CustomNodeModelProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // CustomNodeModelProtocol.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/31. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #ifndef CustomNodeModelProtocol_h 10 | #define CustomNodeModelProtocol_h 11 | #import "NodeModelProtocol.h" 12 | 13 | @protocol CustomNodeModelProtocol 14 | /*自定义属性*/ 15 | @property(nonatomic, strong) id customProperty; 16 | @property(nonatomic, strong) id customProperty1; 17 | @property(nonatomic, strong) id customProperty2; 18 | @property(nonatomic, strong) id customProperty3; 19 | @property(nonatomic, strong) id customProperty4; 20 | /*自定义方法*/ 21 | - (void)customMethod; 22 | @end 23 | 24 | #endif /* CustomNodeModelProtocol_h */ 25 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/CustomProtocols/CustomNodeViewProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // CustomNodeViewProtocol.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/31. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #ifndef CustomNodeViewProtocol_h 10 | #define CustomNodeViewProtocol_h 11 | #import "NodeViewProtocol.h" 12 | 13 | @protocol CustomNodeViewProtocol 14 | /*自定义属性*/ 15 | @property(nonatomic, strong) id customProperty; 16 | @property(nonatomic, strong) id customProperty1; 17 | @property(nonatomic, strong) id customProperty2; 18 | @property(nonatomic, strong) id customProperty3; 19 | @property(nonatomic, strong) id customProperty4; 20 | /*自定义方法*/ 21 | - (void)customMethod; 22 | @end 23 | 24 | #endif /* CustomNodeViewProtocol_h */ 25 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/Model/OrganizationNode.h: -------------------------------------------------------------------------------- 1 | // 2 | // OrganizationNode.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import "BaseTreeNode.h" 10 | 11 | @interface OrganizationNode : BaseTreeNode 12 | /** 13 | 左侧标题 14 | */ 15 | @property (nonatomic, copy) NSString *title; 16 | 17 | @end 18 | //Organization; 19 | 20 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/Model/OrganizationNode.m: -------------------------------------------------------------------------------- 1 | // 2 | // OrganizationNode.m 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import "OrganizationNode.h" 10 | 11 | @implementation OrganizationNode 12 | 13 | - (instancetype)init{ 14 | if (self = [super init]) { 15 | 16 | } 17 | return self; 18 | } 19 | 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/Model/SinglePersonNode.h: -------------------------------------------------------------------------------- 1 | // 2 | // SinglePersonNode.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BaseTreeNode.h" 11 | @interface SinglePersonNode : BaseTreeNode 12 | /** 13 | 姓名 14 | */ 15 | @property (nonatomic, copy) NSString *name; 16 | /** 17 | 工号 18 | */ 19 | @property (nonatomic, copy) NSString *IDNum; 20 | /** 21 | 部门 22 | */ 23 | @property (nonatomic, copy) NSString *dePartment; 24 | /** 25 | 是否选中 26 | */ 27 | @property (nonatomic, assign,getter=isSelected) BOOL selected; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/Model/SinglePersonNode.m: -------------------------------------------------------------------------------- 1 | // 2 | // SinglePersonNode.m 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import "SinglePersonNode.h" 10 | @implementation SinglePersonNode 11 | @end 12 | 13 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/View/Cells/TreeOrganizationDisplayCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // TreeOrganizationDisplayCell.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BaseTreeNode.h" 11 | #import "NodeTreeView.h" 12 | 13 | @interface TreeOrganizationDisplayCell : UITableViewCell 14 | 15 | @property (nonatomic, strong) BaseTreeNode *node; 16 | 17 | @property (nonatomic, copy) void(^selectNode)(BaseTreeNode *node); 18 | 19 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier treeStyle:(NodeTreeViewStyle)treeStyle; 20 | 21 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier treeStyle:(NodeTreeViewStyle)treeStyle treeRefreshPolicy:(NodeTreeRefreshPolicy)policy; 22 | /** 23 | 刷新node节点对应的树 24 | */ 25 | - (void)reloadTreeViewWithNode:(BaseTreeNode *)node RowAnimation:(UITableViewRowAnimation)animation;; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/View/Cells/TreeOrganizationDisplayCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // TreeOrganizationDisplayCell.m 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import "TreeOrganizationDisplayCell.h" 10 | #import "SinglePersonNodeView.h" 11 | #import "OrganizationNodeView.h" 12 | #import "AssistMicros.h" 13 | 14 | @interface TreeOrganizationDisplayCell () 15 | 16 | @property (nonatomic, strong) NodeTreeView *treeView; 17 | 18 | @property (nonatomic, assign) NodeTreeViewStyle treeStyle; 19 | 20 | @property (nonatomic, assign) NodeTreeRefreshPolicy treeRefreshPolicy; 21 | 22 | @end 23 | 24 | @implementation TreeOrganizationDisplayCell 25 | 26 | - (void)awakeFromNib { 27 | [super awakeFromNib]; 28 | // Initialization code 29 | } 30 | 31 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated { 32 | [super setSelected:selected animated:animated]; 33 | 34 | // Configure the view for the selected state 35 | } 36 | 37 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier treeStyle:(NodeTreeViewStyle)treeStyle treeRefreshPolicy:(NodeTreeRefreshPolicy)policy{ 38 | if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { 39 | self.treeStyle = treeStyle; 40 | self.treeRefreshPolicy = policy; 41 | [self setupSubviews]; 42 | } 43 | return self; 44 | } 45 | 46 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier treeStyle:(NodeTreeViewStyle)treeStyle{ 47 | if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { 48 | self.treeStyle = treeStyle; 49 | [self setupSubviews]; 50 | } 51 | return self; 52 | } 53 | 54 | #pragma mark ======== Custom Delegate ======== 55 | 56 | #pragma mark NodeTreeViewDelegate 57 | - (id_Nonnull)nodeTreeView:(NodeTreeView *)treeView viewForNode:(id)node{ 58 | id _node = node; 59 | if ([_node isMemberOfClass:[SinglePersonNode class]]) { 60 | return [[SinglePersonNodeView alloc]initWithFrame:CGRectMake(0, 0,WIDTH, node.nodeHeight)]; 61 | }else if([_node isMemberOfClass:[OrganizationNode class]]){ 62 | return [[OrganizationNodeView alloc]initWithFrame:CGRectMake(0, 0,WIDTH, node.nodeHeight)]; 63 | } 64 | return [[SinglePersonNodeView alloc]initWithFrame:CGRectMake(0, 0,[UIScreen mainScreen].bounds.size.width, node.nodeHeight)]; 65 | } 66 | 67 | - (CGFloat)nodeTreeView:(NodeTreeView *_Nonnull)treeView indentAtNodeLevel:(NSInteger)nodeLevel{ 68 | if (NodeTreeViewStyleBreadcrumbs == self.treeStyle) { 69 | return 0; 70 | }else if (NodeTreeViewStyleExpansion == self.treeStyle){ 71 | if (nodeLevel == 1) { 72 | return 0; 73 | }else{ 74 | return 30*nodeLevel; 75 | } 76 | }else{ 77 | return 0; 78 | } 79 | } 80 | 81 | - (void)nodeTreeView:(NodeTreeView *_Nonnull)treeView didSelectNode:(id_Nonnull)node{ 82 | id selectNode = node; 83 | if ([selectNode isMemberOfClass:[SinglePersonNode class]]) { 84 | SinglePersonNode *personNode = (SinglePersonNode *)selectNode; 85 | if (personNode.subNodes.count == 0) { 86 | personNode.selected = !personNode.selected; 87 | } 88 | } 89 | //通过node来刷新headerView,通过回调传给外界 90 | if (self.selectNode) { 91 | self.selectNode((BaseTreeNode *)node); 92 | } 93 | } 94 | 95 | #pragma mark ======== Private Methods ======== 96 | 97 | - (void)setupSubviews{ 98 | [self.contentView addSubview:self.treeView]; 99 | } 100 | 101 | /** 102 | 刷新node节点对应的树 103 | */ 104 | - (void)reloadTreeViewWithNode:(BaseTreeNode *)node RowAnimation:(UITableViewRowAnimation)animation{ 105 | _node = node; 106 | [_treeView reloadTreeViewWithNode:node RowAnimation:animation]; 107 | } 108 | 109 | #pragma mark ======== Setters && Getters ======== 110 | - (void)setNode:(BaseTreeNode *)node{ 111 | _node = node; 112 | [_treeView reloadTreeViewWithNode:_node]; 113 | } 114 | 115 | - (NodeTreeView *)treeView{ 116 | if (!_treeView) { 117 | _treeView = [[NodeTreeView alloc]initWithFrame:CGRectMake(0, 0, WIDTH, self.frame.size.height) treeViewStyle:self.treeStyle]; 118 | _treeView.treeDelegate = self; 119 | _treeView.refreshPolicy = self.treeRefreshPolicy; 120 | } 121 | return _treeView; 122 | } 123 | @end 124 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/View/NodeViews/OrganizationNodeView.h: -------------------------------------------------------------------------------- 1 | // 2 | // OrganizationNodeView.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "OrganizationNode.h" 11 | #import "NodeViewProtocol.h" 12 | @interface OrganizationNodeView : UIView 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/View/NodeViews/OrganizationNodeView.m: -------------------------------------------------------------------------------- 1 | // 2 | // OrganizationNodeView.m 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import "OrganizationNodeView.h" 10 | 11 | @interface OrganizationNodeView () 12 | 13 | @property (nonatomic, strong) UILabel *titleLabel; 14 | 15 | @property (nonatomic, strong) UIImageView *rightImgView; 16 | 17 | @end 18 | 19 | @implementation OrganizationNodeView 20 | 21 | /* 22 | // Only override drawRect: if you perform custom drawing. 23 | // An empty implementation adversely affects performance during animation. 24 | - (void)drawRect:(CGRect)rect { 25 | // Drawing code 26 | } 27 | */ 28 | - (instancetype)initWithFrame:(CGRect)frame{ 29 | if (self = [super initWithFrame:frame]) { 30 | [self setupSubviews]; 31 | } 32 | return self; 33 | } 34 | 35 | #pragma mark ======== Custom Delegate ======== 36 | 37 | #pragma mark NodeViewProtocol 38 | - (void)updateNodeViewWithNodeModel:(id)node{ 39 | //将node转为该view对应的指定node,然后执行操作 40 | OrganizationNode *simpleNode = (OrganizationNode *)node; 41 | self.titleLabel.text = simpleNode.title; 42 | } 43 | 44 | - (void)layoutSubviews{ 45 | [super layoutSubviews]; 46 | self.titleLabel.frame = CGRectMake(12, 0, 200, self.frame.size.height); 47 | self.rightImgView.frame = CGRectMake(self.frame.size.width - 12 -12, self.frame.size.height/2-6, 12, 12); 48 | } 49 | 50 | #pragma mark ======== Private Methods ======== 51 | 52 | - (void)setupSubviews{ 53 | [self addSubview:self.titleLabel]; 54 | [self addSubview:self.rightImgView]; 55 | } 56 | 57 | #pragma mark ======== Setters && Getters ======== 58 | - (UILabel *)titleLabel{ 59 | if (!_titleLabel) { 60 | _titleLabel = [[UILabel alloc]init]; 61 | _titleLabel.textColor = [UIColor blackColor]; 62 | _titleLabel.font = [UIFont systemFontOfSize:14]; 63 | _titleLabel.textAlignment = NSTextAlignmentLeft; 64 | 65 | } 66 | return _titleLabel; 67 | } 68 | 69 | - (UIImageView *)rightImgView{ 70 | if (!_rightImgView) { 71 | _rightImgView = [[UIImageView alloc]init]; 72 | _rightImgView.image = [UIImage imageNamed:@"next"]; 73 | 74 | } 75 | return _rightImgView; 76 | } 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/View/NodeViews/SinglePersonNodeView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SinglePersonNodeView.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "NodeViewProtocol.h" 11 | #import "SinglePersonNode.h" 12 | 13 | @interface SinglePersonNodeView : UIView 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/View/NodeViews/SinglePersonNodeView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SinglePersonNodeView.m 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import "SinglePersonNodeView.h" 10 | 11 | @interface SinglePersonNodeView () 12 | /** 13 | 姓名 14 | */ 15 | @property (nonatomic, strong) UILabel *nameLabel; 16 | /** 17 | 工号 18 | */ 19 | @property (nonatomic, strong) UILabel *IDLabel; 20 | /** 21 | 部门 22 | */ 23 | @property (nonatomic, strong) UILabel *departmentLabel; 24 | /** 25 | 选择按钮 26 | */ 27 | @property (nonatomic, strong) UIButton *selectBtn; 28 | 29 | @end 30 | 31 | @implementation SinglePersonNodeView 32 | 33 | /* 34 | // Only override drawRect: if you perform custom drawing. 35 | // An empty implementation adversely affects performance during animation. 36 | - (void)drawRect:(CGRect)rect { 37 | // Drawing code 38 | } 39 | */ 40 | - (instancetype)initWithFrame:(CGRect)frame{ 41 | if (self = [super initWithFrame:frame]) { 42 | [self setupSubviews]; 43 | } 44 | return self; 45 | } 46 | 47 | #pragma mark ======== Custom Delegate ======== 48 | 49 | #pragma mark NodeViewProtocol 50 | - (void)updateNodeViewWithNodeModel:(id)node{ 51 | //将node转为该view对应的指定node,然后执行操作 52 | SinglePersonNode *personNode = (SinglePersonNode *)node; 53 | if (personNode.selected == YES) { 54 | self.selectBtn.selected = YES; 55 | }else{ 56 | self.selectBtn.selected = NO; 57 | 58 | } 59 | _nameLabel.text = personNode.name; 60 | _IDLabel.text = personNode.IDNum; 61 | _departmentLabel.text = personNode.dePartment; 62 | } 63 | 64 | - (void)layoutSubviews{ 65 | [super layoutSubviews]; 66 | _selectBtn.frame = CGRectMake(12, self.frame.size.height/2-6, 12, 12); 67 | _nameLabel.frame = CGRectMake(12+12+12, 0, 60, self.frame.size.height); 68 | _IDLabel.frame = CGRectMake(12+12+12+60+12, self.frame.size.height/2-7, 60, 14); 69 | _departmentLabel.frame = CGRectMake(12+12+12+60+12+60+12, 0, self.frame.size.width-(12+12+12+60+12+60+12+12), self.frame.size.height); 70 | } 71 | 72 | #pragma mark ======== Private Methods ======== 73 | 74 | - (void)setupSubviews{ 75 | [self addSubview:self.selectBtn]; 76 | [self addSubview:self.nameLabel]; 77 | [self addSubview:self.IDLabel]; 78 | [self addSubview:self.departmentLabel]; 79 | } 80 | 81 | - (void)btnSelect:(UIButton *)btn{ 82 | btn.selected = !btn.selected; 83 | } 84 | 85 | #pragma mark ======== Setters && Getters ======== 86 | 87 | - (UILabel *)nameLabel{ 88 | if (!_nameLabel) { 89 | _nameLabel = [[UILabel alloc]init]; 90 | _nameLabel.font = [UIFont systemFontOfSize:14]; 91 | _nameLabel.textColor = [UIColor blackColor]; 92 | _nameLabel.numberOfLines = 0; 93 | _nameLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; 94 | 95 | } 96 | return _nameLabel; 97 | } 98 | 99 | - (UILabel *)IDLabel{ 100 | if (!_IDLabel) { 101 | _IDLabel = [[UILabel alloc]init]; 102 | _IDLabel.font = [UIFont systemFontOfSize:14]; 103 | _IDLabel.textColor = [UIColor blackColor]; 104 | } 105 | return _IDLabel; 106 | } 107 | 108 | - (UILabel *)departmentLabel{ 109 | if (!_departmentLabel) { 110 | _departmentLabel = [[UILabel alloc]init]; 111 | _departmentLabel.font = [UIFont systemFontOfSize:14]; 112 | _departmentLabel.textColor = [UIColor blackColor]; 113 | _departmentLabel.numberOfLines = 0; 114 | _departmentLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; 115 | } 116 | return _departmentLabel; 117 | } 118 | 119 | - (UIButton *)selectBtn{ 120 | if (!_selectBtn) { 121 | _selectBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 122 | [_selectBtn setImage:[UIImage imageNamed:@"node_normal"] forState:UIControlStateNormal]; 123 | [_selectBtn setImage:[UIImage imageNamed:@"node_selected"] forState:UIControlStateSelected]; 124 | } 125 | return _selectBtn; 126 | } 127 | 128 | @end 129 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/View/Others/BreadcrumbHeaderView.h: -------------------------------------------------------------------------------- 1 | // 2 | // BreadcrumbHeaderView.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BaseTreeNode.h" 11 | 12 | @interface BreadcrumbHeaderView : UIScrollView 13 | 14 | @property (nonatomic, copy) void(^selectNode)(BaseTreeNode *node,UITableViewRowAnimation nodeTreeAnimation); 15 | 16 | - (void)addSelectedNode:(BaseTreeNode *)node withTitle:(NSString *)title; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /TreeNodeStructure/Examples/View/Others/BreadcrumbHeaderView.m: -------------------------------------------------------------------------------- 1 | // 2 | // BreadcrumbHeaderView.m 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import "BreadcrumbHeaderView.h" 10 | 11 | @interface NSString (BreadcrumbStringSize) 12 | - (CGFloat)widthForFont:(UIFont *)font; 13 | @end 14 | 15 | @implementation NSString (BreadcrumbStringSize) 16 | - (CGFloat)widthForFont:(UIFont *)font { 17 | CGSize size = [self sizeForFont:font size:CGSizeMake(HUGE, HUGE) mode:NSLineBreakByWordWrapping]; 18 | return size.width; 19 | } 20 | 21 | - (CGSize)sizeForFont:(UIFont *)font size:(CGSize)size mode:(NSLineBreakMode)lineBreakMode { 22 | CGSize result; 23 | if (!font) font = [UIFont systemFontOfSize:12]; 24 | if ([self respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)]) { 25 | NSMutableDictionary *attr = [NSMutableDictionary new]; 26 | attr[NSFontAttributeName] = font; 27 | if (lineBreakMode != NSLineBreakByWordWrapping) { 28 | NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; 29 | paragraphStyle.lineBreakMode = lineBreakMode; 30 | attr[NSParagraphStyleAttributeName] = paragraphStyle; 31 | } 32 | CGRect rect = [self boundingRectWithSize:size 33 | options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading 34 | attributes:attr context:nil]; 35 | result = rect.size; 36 | } else { 37 | #pragma clang diagnostic push 38 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 39 | result = [self sizeWithFont:font constrainedToSize:size lineBreakMode:lineBreakMode]; 40 | #pragma clang diagnostic pop 41 | } 42 | return result; 43 | } 44 | 45 | @end 46 | 47 | @interface BreadcrumbHeaderView () 48 | 49 | @property (nonatomic, strong) NSMutableArray *allNodes; 50 | 51 | @property (nonatomic, strong) NSMutableArray *allTitles; 52 | 53 | @end 54 | 55 | @implementation BreadcrumbHeaderView 56 | /* 57 | // Only override drawRect: if you perform custom drawing. 58 | // An empty implementation adversely affects performance during animation. 59 | - (void)drawRect:(CGRect)rect { 60 | // Drawing code 61 | } 62 | */ 63 | 64 | - (void)addSelectedNode:(BaseTreeNode *)node withTitle:(NSString *)title{ 65 | [self.allNodes addObject:node]; 66 | [self.allTitles addObject:title]; 67 | [self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; 68 | CGFloat left = 12; 69 | if (self.allTitles.count == self.allNodes.count) { 70 | for (int i = 0; i",self.allTitles[i]] forState:UIControlStateNormal]; 84 | btnWidth = [btn.titleLabel.text widthForFont:[UIFont systemFontOfSize:14]]+6; 85 | } 86 | btn.frame = CGRectMake(left, 0, btnWidth, self.frame.size.height); 87 | left += btnWidth; 88 | if (left>[UIScreen mainScreen].bounds.size.width) { 89 | self.contentSize = CGSizeMake(left+([UIScreen mainScreen].bounds.size.width)/2,0); 90 | }else{ 91 | self.contentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width,0); 92 | } 93 | [self addSubview:btn]; 94 | } 95 | } 96 | } 97 | 98 | - (void)btnClick:(UIButton *)btn{ 99 | if (self.subviews.lastObject == btn) { 100 | return; 101 | } 102 | NSInteger index = btn.tag - 1000; 103 | BaseTreeNode *node = self.allNodes[index]; 104 | if (node.subNodes.count>0) { 105 | [self.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 106 | if (obj.tag >= btn.tag) { 107 | [obj removeFromSuperview]; 108 | [self.allNodes removeLastObject]; 109 | [self.allTitles removeLastObject]; 110 | } 111 | }]; 112 | } 113 | if (self.selectNode) { 114 | self.selectNode(node,UITableViewRowAnimationNone); 115 | } 116 | } 117 | 118 | - (NSMutableArray *)allNodes{ 119 | if (!_allNodes) { 120 | _allNodes = [NSMutableArray array]; 121 | } 122 | return _allNodes; 123 | } 124 | 125 | - (NSMutableArray *)allTitles{ 126 | if (!_allTitles) { 127 | _allTitles = [NSMutableArray array]; 128 | } 129 | return _allTitles; 130 | } 131 | 132 | @end 133 | -------------------------------------------------------------------------------- /TreeNodeStructure/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /TreeNodeStructure/TreeViewTemplate/Nodes/BaseTreeNode.h: -------------------------------------------------------------------------------- 1 | // 2 | // BaseTreeNode.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 自定义的node可以继承自这个类,也可以直接遵循NodeModelProtocol协议,然后自行配置 8 | 9 | #import 10 | #import "NodeModelProtocol.h" 11 | @interface BaseTreeNode : NSObject 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /TreeNodeStructure/TreeViewTemplate/Nodes/BaseTreeNode.m: -------------------------------------------------------------------------------- 1 | // 2 | // BaseTreeNode.m 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import "BaseTreeNode.h" 10 | 11 | CGFloat treeHeight; 12 | CGFloat tempNodeLevel; 13 | 14 | #pragma mark 获取根节点 15 | static inline idRecursiveGetRootNodeWithNode(id node){ 16 | if (node.fatherNode == node) { 17 | node.expand = YES; 18 | return node; 19 | }else{ 20 | node = node.fatherNode; 21 | tempNodeLevel = tempNodeLevel+1; 22 | return RecursiveGetRootNodeWithNode(node); 23 | } 24 | } 25 | 26 | #pragma mark 根据根节点获取树的高度 27 | static inline void RecursiveCalculateTreeHeightWithRootNode(id rootNode){ 28 | if (rootNode.subNodes.count == 0||!rootNode.isExpand) { 29 | return ; 30 | } 31 | if (!isnan(rootNode.subTreeHeight)) { 32 | treeHeight += rootNode.subTreeHeight; 33 | } 34 | for (idobj in rootNode.subNodes) { 35 | RecursiveCalculateTreeHeightWithRootNode(obj); 36 | } 37 | } 38 | 39 | @implementation BaseTreeNode 40 | 41 | @synthesize 42 | subNodes = _subNodes, 43 | nodeHeight = _nodeHeight, 44 | nodeID = _nodeID, 45 | fatherNode = _fatherNode, 46 | /*NodeTreeViewStyleBreadcrumbs*/ 47 | subTreeHeight = _subTreeHeight, 48 | /*NodeTreeViewStyleExpansion*/ 49 | expand = _expand, 50 | currentTreeHeight = _currentTreeHeight, 51 | nodeLevel = _nodeLevel; 52 | 53 | - (instancetype)init{ 54 | if (self = [super init]) { 55 | _subNodes = [NSMutableArray array]; 56 | _nodeHeight = 44; 57 | _subTreeHeight = 0; 58 | } 59 | return self; 60 | } 61 | 62 | - (CGFloat)subTreeHeight{ 63 | if (!_subTreeHeight) { 64 | CGFloat tempSubTreeHeight = 0; 65 | for (id _Nonnull obj in self.subNodes) { 66 | tempSubTreeHeight += obj.nodeHeight; 67 | } 68 | _subTreeHeight = tempSubTreeHeight; 69 | } 70 | return _subTreeHeight; 71 | } 72 | 73 | - (CGFloat)currentTreeHeight{ 74 | treeHeight = _currentTreeHeight = 0; 75 | if (self.fatherNode) { 76 | id rootNode = RecursiveGetRootNodeWithNode(self); 77 | if (rootNode == nil) { 78 | NSLog(@"未获取到rootNode"); 79 | }else{ 80 | RecursiveCalculateTreeHeightWithRootNode(rootNode); 81 | _currentTreeHeight = treeHeight; 82 | } 83 | } 84 | return _currentTreeHeight; 85 | } 86 | 87 | - (NSInteger)nodeLevel{ 88 | if (!_nodeLevel || _nodeLevel == 0) { 89 | tempNodeLevel = 0; 90 | id rootNode = RecursiveGetRootNodeWithNode(self); 91 | rootNode.nodeLevel = 0; 92 | _nodeLevel = tempNodeLevel; 93 | } 94 | return _nodeLevel; 95 | } 96 | 97 | - (void)getTreeHeightAtFatherNode:(id)fatherNode{ 98 | if (fatherNode.subNodes.count == 0||!fatherNode.isExpand) {//叶子节点 99 | return ; 100 | } 101 | if (!isnan(fatherNode.subTreeHeight)) { 102 | _currentTreeHeight += fatherNode.subTreeHeight; 103 | } 104 | for (idobj in fatherNode.subNodes) { 105 | [self getTreeHeightAtFatherNode:obj]; 106 | } 107 | } 108 | 109 | - (void)addSubNode:(id)node{ 110 | node.fatherNode = self; 111 | [self.subNodes addObject:node]; 112 | } 113 | 114 | - (void)addSubNodesFromArray:(NSArray> *)nodes{ 115 | [nodes enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 116 | obj.fatherNode = self; 117 | }]; 118 | [self.subNodes addObjectsFromArray:nodes]; 119 | } 120 | 121 | - (void)deleteSubNode:(id)node{ 122 | [self.subNodes removeObject:node]; 123 | } 124 | 125 | @end 126 | -------------------------------------------------------------------------------- /TreeNodeStructure/TreeViewTemplate/Protocols/NodeModelProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // NodeModelProtocol.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 统一模型,节点模型都需要遵循该协议 8 | 9 | #ifndef NodeModelProtocol_h 10 | #define NodeModelProtocol_h 11 | #import 12 | #import 13 | 14 | @protocol NodeModelProtocol 15 | @required 16 | /** 17 | 该节点元素自身的高度 18 | */ 19 | @property (nonatomic, assign) CGFloat nodeHeight; 20 | /** 21 | 该节点元素对应的所有子节点,添加子节点的时候,不要直接向数组中添加,而应该通过addNode的方式,这样会自动设置父节点 22 | */ 23 | @property (nonatomic, strong) NSMutableArray> *subNodes; 24 | /** 25 | 添加node节点 26 | 27 | @param node node节点 28 | */ 29 | - (void)addSubNode:(id)node; 30 | /** 31 | 删除节点 32 | 33 | @param node node节点 34 | */ 35 | - (void)deleteSubNode:(id)node; 36 | /** 37 | 父节点,注意根节点的父节点需要手动设置为自身 38 | */ 39 | @property (nonatomic, weak) id fatherNode; 40 | /** 41 | 该节点相对于根节点处于第几级 42 | */ 43 | @property (nonatomic, assign) NSInteger nodeLevel; 44 | 45 | @optional 46 | /** 47 | ID标记 48 | */ 49 | @property (nonatomic, copy) NSString *nodeID; 50 | /** 51 | 该节点展开后的所有儿子节点的高度之和 52 | */ 53 | @property (nonatomic, assign) CGFloat subTreeHeight; 54 | /** 55 | 该节点所在树的当前整体高度 56 | */ 57 | @property (nonatomic, assign) CGFloat currentTreeHeight; 58 | /** 59 | 在NodeTreeViewStyleExpansion模式下,需要确定该节点是否展开 60 | */ 61 | @property (nonatomic, assign, getter = isExpand) BOOL expand; 62 | /** 63 | 从node数组中添加节点 64 | 65 | @param nodes nodes数组 66 | */ 67 | - (void)addSubNodesFromArray:(NSArray> *)nodes; 68 | 69 | @end 70 | 71 | #endif /* NodeModelProtocol_h */ 72 | -------------------------------------------------------------------------------- /TreeNodeStructure/TreeViewTemplate/Protocols/NodeViewProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // NodeViewProtocol.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 统一视图,节点视图都需要遵循该协议 8 | 9 | #ifndef NodeViewProtocol_h 10 | #define NodeViewProtocol_h 11 | #import "NodeModelProtocol.h" 12 | 13 | @protocol NodeViewProtocol 14 | @required 15 | /** 16 | 更新单个Node行 17 | 18 | @param node node模型 19 | */ 20 | - (void)updateNodeViewWithNodeModel:(id)node; 21 | /** 22 | 需要在该方法中,对cell进行重新布局,为了处理在缩进的时候宽度变化造成的影响 23 | */ 24 | - (void)layoutSubviews; 25 | 26 | @end 27 | 28 | #endif /* NodeViewProtocol_h */ 29 | -------------------------------------------------------------------------------- /TreeNodeStructure/TreeViewTemplate/Views/NodeTreeView.h: -------------------------------------------------------------------------------- 1 | // 2 | // NodeTreeView.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "NodeModelProtocol.h" 11 | #import "NodeViewProtocol.h" 12 | 13 | typedef NS_ENUM(NSUInteger,NodeTreeViewStyle) 14 | { 15 | NodeTreeViewStyleBreadcrumbs = 1, //面包屑形式 16 | NodeTreeViewStyleExpansion //展开形式 17 | }; 18 | 19 | typedef NS_ENUM(NSUInteger,NodeTreeRefreshPolicy) 20 | { 21 | NodeTreeRefreshPolicyManaul = 1, //手动形式,常用于网络数据源 22 | NodeTreeRefreshPolicyAutomic //自动刷新,常用于本地数据源 23 | }; 24 | @class NodeTreeView; 25 | 26 | @protocol NodeTreeViewDelegate 27 | @required 28 | /** 29 | 返回对应节点下的视图:视图可以遵循NodeViewProtocol协议,让view具有一些统一的行为> 30 | 一种node对应一种nodeView 31 | 32 | @param node node节点 33 | @return node视图 34 | */ 35 | - (id_Nonnull)nodeTreeView:(NodeTreeView *_Nonnull)treeView viewForNode:(id_Nonnull)node; 36 | 37 | @optional 38 | 39 | /** 40 | 返回对应级别下的缩进 41 | 42 | @param treeView treeView 43 | @param nodeLevel 对应的nodeLevel 44 | @return 该level下对应的缩进 45 | */ 46 | - (CGFloat)nodeTreeView:(NodeTreeView *_Nonnull)treeView indentAtNodeLevel:(NSInteger)nodeLevel; 47 | /** 48 | 点击事件回调 49 | 50 | @param treeView 树 51 | @param node 节点模型 52 | */ 53 | - (void)nodeTreeView:(NodeTreeView *_Nonnull)treeView didSelectNode:(id_Nonnull)node; 54 | 55 | @end 56 | 57 | @interface NodeTreeView : UIScrollView 58 | /** 59 | 树的刷新策略 60 | 默认是手动刷新:NodeTreeRefreshPolicyManaul 61 | */ 62 | @property (nonatomic, assign) NodeTreeRefreshPolicy refreshPolicy; 63 | /** 64 | 代理 65 | */ 66 | @property (nonatomic, assign) id_Nonnull treeDelegate; 67 | /** 68 | 统一设置缩进 69 | */ 70 | @property (nonatomic, assign) CGFloat indepent; 71 | /** 72 | 初始化方法 73 | 74 | @param frame frame 75 | @param style 展现形式 76 | @return treeView实例 77 | */ 78 | - (instancetype _Nullable )initWithFrame:(CGRect)frame treeViewStyle:(NodeTreeViewStyle)style; 79 | /** 80 | 刷新node节点对应的树 81 | */ 82 | - (void)reloadTreeViewWithNode:(id_Nonnull)node; 83 | /** 84 | 刷新node节点对应的树,可以指定动画展开的方式 85 | 86 | @param node node节点 87 | 88 | */ 89 | - (void)reloadTreeViewWithNode:(id_Nonnull)node 90 | RowAnimation:(UITableViewRowAnimation)animation; 91 | /** 92 | 返回对应节点的view 93 | 94 | @param node 节点 95 | @return 该节点对应的nodeview 96 | */ 97 | - (id_Nonnull)nodeViewForNode:(id_Nonnull)node; 98 | 99 | @end 100 | -------------------------------------------------------------------------------- /TreeNodeStructure/TreeViewTemplate/Views/NodeTreeView.m: -------------------------------------------------------------------------------- 1 | // 2 | // NodeTreeView.m 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import "NodeTreeView.h" 10 | 11 | #pragma mark 判断树在某一节点是否应该收起 12 | static inline bool TreeShouldFoldAtNode(BOOL autoRefresh, id node){ 13 | BOOL shouldFold; 14 | if (autoRefresh) {//自动刷新 15 | if (node.isExpand) { 16 | shouldFold = YES; 17 | }else{ 18 | shouldFold = NO; 19 | } 20 | }else{ //手动刷新刷新 21 | if (node.isExpand) { 22 | shouldFold = NO; 23 | }else{ 24 | shouldFold = YES; 25 | } 26 | } 27 | return shouldFold; 28 | } 29 | 30 | #pragma mark NodeTreeViewStyleExpansion模式下,初始化数据源,递归的将所有需要展开的节点,加入数据源中 31 | static inline void RecursiveInitializeAllNodesWithRootNode(NSMutableArray *allNodes,idrootNode){ 32 | if (rootNode.expand == NO || rootNode.subNodes.count == 0) { 33 | return; 34 | }else{ 35 | if (allNodes.count == 0) { 36 | [allNodes addObjectsFromArray:rootNode.subNodes]; 37 | }else{ 38 | NSUInteger beginPosition = [allNodes indexOfObject:rootNode] + 1; 39 | NSUInteger endPosition = beginPosition + rootNode.subNodes.count - 1; 40 | NSRange range = NSMakeRange(beginPosition, endPosition-beginPosition+1); 41 | NSIndexSet *set = [NSIndexSet indexSetWithIndexesInRange:range]; 42 | [allNodes insertObjects:rootNode.subNodes atIndexes:set]; 43 | } 44 | for (idsubNode in rootNode.subNodes) { 45 | rootNode = subNode; 46 | RecursiveInitializeAllNodesWithRootNode(allNodes, rootNode); 47 | } 48 | } 49 | } 50 | 51 | #pragma mark 递归的将某节点下所有子节点的展开状态置为NO 52 | static inline void RecursiveFoldAllSubnodesAtNode(idnode){ 53 | if (node.subNodes.count>0) { 54 | [node.subNodes enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 55 | if (obj.isExpand) { 56 | obj.expand = NO; 57 | RecursiveFoldAllSubnodesAtNode(node); 58 | } 59 | }]; 60 | }else{ 61 | return; 62 | } 63 | } 64 | 65 | #pragma mark 递归的向view的所有子view发送setNeedsLayout消息,进行重新布局 66 | static inline void RecursiveLayoutSubviews(UIView *_Nonnull view){ 67 | if (view.subviews.count == 0) { 68 | return; 69 | }else{ 70 | [view.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull subView, NSUInteger idx, BOOL * _Nonnull stop) { 71 | [subView setNeedsLayout]; 72 | RecursiveLayoutSubviews(subView); 73 | }]; 74 | } 75 | } 76 | 77 | #pragma mark NodeTreeViewCell 78 | @interface NodeTreeViewCell:UITableViewCell 79 | 80 | @property (nonatomic,strong)idnodeView; 81 | 82 | @end 83 | 84 | #pragma mark NodeTreeView 85 | @interface NodeTreeView () 86 | { 87 | struct { 88 | unsigned int indentAtNodelevel:1; 89 | unsigned int didSelectNode:1; 90 | unsigned int viewForNode:1; 91 | }_treeViewDelegateFalg; 92 | 93 | } 94 | @property (nonatomic, strong) UITableView *tableview; 95 | 96 | @property (nonatomic, strong) NSMutableArray >*allNodes; 97 | 98 | @property (nonatomic, strong) idcurrentNode; 99 | 100 | @property (nonatomic, assign) NodeTreeViewStyle treeViewStyle; 101 | 102 | @property (nonatomic, assign,getter=isAutoRefresh) BOOL autoRefresh; 103 | 104 | @end 105 | 106 | 107 | @implementation NodeTreeView 108 | 109 | /* 110 | // Only override drawRect: if you perform custom drawing. 111 | // An empty implementation adversely affects performance during animation. 112 | - (void)drawRect:(CGRect)rect { 113 | // Drawing code 114 | } 115 | */ 116 | 117 | - (instancetype)init{ 118 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Must use initWithFrame: treeViewStyle: instead" userInfo:nil]; 119 | } 120 | 121 | - (instancetype)initWithFrame:(CGRect)frame{ 122 | return [self initWithFrame:frame treeViewStyle:NodeTreeViewStyleBreadcrumbs]; 123 | } 124 | 125 | - (instancetype)initWithFrame:(CGRect)frame treeViewStyle:(NodeTreeViewStyle)style{ 126 | if (self = [super initWithFrame:frame]) { 127 | self.treeViewStyle = style; 128 | self.alwaysBounceVertical = NO; 129 | self.bounces = YES; 130 | self.showsHorizontalScrollIndicator = YES; 131 | self.scrollEnabled = self.treeViewStyle == NodeTreeViewStyleExpansion; 132 | self.refreshPolicy = NodeTreeRefreshPolicyManaul; 133 | [self addSubview:self.tableview]; 134 | 135 | } 136 | return self; 137 | } 138 | 139 | #pragma mark ======== System Delegate ======== 140 | 141 | #pragma mark DataSource 142 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ 143 | if (NodeTreeViewStyleBreadcrumbs == self.treeViewStyle) { 144 | return self.currentNode.subNodes.count; 145 | }else{ 146 | return self.allNodes.count; 147 | } 148 | } 149 | 150 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ 151 | idnode; 152 | if (NodeTreeViewStyleBreadcrumbs == self.treeViewStyle) { 153 | node = self.currentNode.subNodes[indexPath.row]; 154 | }else{ 155 | node = self.allNodes[indexPath.row]; 156 | } 157 | return node.nodeHeight; 158 | } 159 | 160 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ 161 | idnode; 162 | id delegate = self.treeDelegate; 163 | if (NodeTreeViewStyleBreadcrumbs == self.treeViewStyle) { 164 | node = self.currentNode.subNodes[indexPath.row]; 165 | }else{ 166 | node = self.allNodes[indexPath.row]; 167 | } 168 | static NSString *CELLID = @""; 169 | NodeTreeViewCell *cell = [[NodeTreeViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CELLID]; 170 | if (delegate) { 171 | if (_treeViewDelegateFalg.viewForNode) { 172 | [cell setNodeView:[self.treeDelegate nodeTreeView:self viewForNode:node]]; 173 | }else{ 174 | NSString *error = [NSString stringWithFormat:@"NodeTreeView:%@ failed to obtain a nodeView from its delegate",self]; 175 | NSAssert(_treeViewDelegateFalg.viewForNode, error); 176 | } 177 | } 178 | //刷新nodeView 179 | [cell.nodeView updateNodeViewWithNodeModel:node]; 180 | //设置缩进 181 | if (self.indepent>0) { 182 | [cell.contentView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 183 | obj.frame = CGRectMake(node.nodeLevel * self.indepent, 0, cell.contentView.frame.size.width - node.nodeLevel * self.indepent, obj.frame.size.height); 184 | }]; 185 | }else{ 186 | 187 | if (_treeViewDelegateFalg.indentAtNodelevel) {//delegate && [delegate respondsToSelector:@selector(nodeTreeView:indentAtNodeLevel:) 188 | CGFloat indentationWidth = [delegate nodeTreeView:self indentAtNodeLevel:node.nodeLevel]; 189 | [cell.contentView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 190 | obj.frame = CGRectMake(indentationWidth, 0,tableView.frame.size.width-indentationWidth, obj.frame.size.height); 191 | }]; 192 | } 193 | } 194 | //改变高度 195 | UIView *nodeView = (UIView *)cell.nodeView; 196 | nodeView.frame = CGRectMake(nodeView.frame.origin.x, nodeView.frame.origin.y, nodeView.frame.size.width, node.nodeHeight); 197 | //递归调用所有子view的layoutSubviews方法,重新布局 198 | RecursiveLayoutSubviews(nodeView); 199 | return cell; 200 | } 201 | 202 | #pragma mark Delegate 203 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ 204 | idnode; 205 | if (NodeTreeViewStyleBreadcrumbs == self.treeViewStyle) { 206 | node = self.currentNode.subNodes[indexPath.row]; 207 | }else{ 208 | node = self.allNodes[indexPath.row]; 209 | } 210 | //判断刷新方式 211 | if (self.autoRefresh) { 212 | if (node.subNodes.count>0) { 213 | //自动刷新,不需要立马改变node的展开状态,RowAnimation刷新动画是固定的 214 | [self reloadTreeViewWithNode:node]; 215 | } 216 | 217 | }else{ 218 | if (node.subNodes.count>0) { 219 | //手动刷新,需要第一时间改变node的展开状态,由外界控制刷新时机和RowAnimation刷新动画 220 | node.expand = !node.expand; 221 | } 222 | } 223 | 224 | //告知代理 225 | if (_treeViewDelegateFalg.didSelectNode) { 226 | [self.treeDelegate nodeTreeView:self didSelectNode:node]; 227 | } 228 | //如果是自动刷新,则在代理方法执行完后会自动执行刷新方法 229 | if (self.autoRefresh) { 230 | id nodeView = [self nodeViewForNode:node]; 231 | if ([nodeView respondsToSelector:@selector(updateNodeViewWithNodeModel:)]) { 232 | [nodeView updateNodeViewWithNodeModel:node]; 233 | } 234 | } 235 | } 236 | 237 | #pragma mark ======== Private Methods ======== 238 | 239 | - (void)reloadTreeViewWithNode:(id_Nonnull)node{ 240 | if (NodeTreeViewStyleBreadcrumbs == self.treeViewStyle) { 241 | [self reloadTreeViewWithNode:node RowAnimation:UITableViewRowAnimationLeft]; 242 | }else{ 243 | [self reloadTreeViewWithNode:node RowAnimation:UITableViewRowAnimationAutomatic]; 244 | } 245 | } 246 | 247 | - (void)reloadTreeViewWithNode:(id_Nonnull)node RowAnimation:(UITableViewRowAnimation)animation{ 248 | if (NodeTreeViewStyleBreadcrumbs == self.treeViewStyle) { 249 | self.frame = CGRectMake(0, 0, self.frame.size.width, node.subTreeHeight); 250 | _tableview.frame = self.bounds;//self.bounds 251 | self.currentNode = node; 252 | [self.tableview reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 1)] withRowAnimation:animation]; 253 | }else{ 254 | if (self.allNodes.count == 0) { 255 | //默认根节点展开,不然没有意义 256 | node.expand = YES; 257 | //初始化数据源,递归的将所有需要展开的节点,加入数据源中 258 | RecursiveInitializeAllNodesWithRootNode(self.allNodes, node); 259 | self.frame = CGRectMake(0, 0, self.frame.size.width, node.currentTreeHeight); 260 | _tableview.frame = self.bounds; 261 | [self.tableview reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 1)] withRowAnimation:animation]; 262 | }else{ 263 | if (node.subNodes.count == 0) {//没有子节点,不需要展开也不需要收起,刷新指定cell即可 264 | NSIndexPath *selectIndexPath = [NSIndexPath indexPathForRow:[self.allNodes indexOfObject:node] inSection:0]; 265 | [self.tableview reloadRowsAtIndexPaths:@[selectIndexPath] withRowAnimation:animation]; 266 | }else{ 267 | NSUInteger beginPosition = [self.allNodes indexOfObject:node] + 1; 268 | NSUInteger endPosition; 269 | if (TreeShouldFoldAtNode(self.autoRefresh, node)) {//需要收起所有子节点 270 | id fatherNode = node.fatherNode; 271 | if (fatherNode!=nil && fatherNode != node) {//有父节点,并且自身不是根节点,获取跟该node节点在同一级别下的下一兄弟节点,这之间的所有元素都删除掉 272 | if (node.fatherNode.subNodes.lastObject == node) {//该node节点是不是最后一个节点 273 | endPosition = beginPosition + node.subNodes.count - 1; 274 | }else{ 275 | id brotherNode = [node.fatherNode.subNodes objectAtIndex:[node.fatherNode.subNodes indexOfObject:node]+1]; 276 | endPosition = [self.allNodes indexOfObject:brotherNode]-1; 277 | } 278 | }else{ 279 | return; 280 | } 281 | }else{//需要展开 282 | endPosition = beginPosition + node.subNodes.count - 1; 283 | } 284 | if (beginPosition < 1) { 285 | return; 286 | } 287 | NSMutableArray *indexPathes = [NSMutableArray array]; 288 | for (NSUInteger i = beginPosition; i<=endPosition; i++) { 289 | NSIndexPath *indexpath = [NSIndexPath indexPathForRow:i inSection:0]; 290 | [indexPathes addObject:indexpath]; 291 | } 292 | NSRange range = NSMakeRange(beginPosition, endPosition-beginPosition+1); 293 | NSIndexSet *set = [NSIndexSet indexSetWithIndexesInRange:range]; 294 | if (TreeShouldFoldAtNode(self.autoRefresh, node)) {//node本身处于展开状态,此时应该收起来,同时收起它的所有子节点,并且将所有展开的子节点的expand值变为NO 295 | if (self.autoRefresh) { 296 | node.expand = !node.expand; 297 | } 298 | RecursiveFoldAllSubnodesAtNode(node); 299 | self.frame = CGRectMake(0, 0, self.frame.size.width,node.currentTreeHeight);//self.frame.size.height-deleteHeight 300 | _tableview.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height+2);//self.bounds 301 | //self.contentSize = CGSizeMake(0, self.frame.size.height+12); 302 | [self.allNodes removeObjectsAtIndexes:set]; 303 | [_tableview deleteRowsAtIndexPaths:indexPathes withRowAnimation:animation]; 304 | }else{ //node本身处于收起来的状态,此时应该展开 305 | if (self.autoRefresh) { 306 | node.expand = !node.expand; 307 | } 308 | self.frame = CGRectMake(0, 0, self.frame.size.width,node.currentTreeHeight); 309 | _tableview.frame = self.bounds; 310 | [self.allNodes insertObjects:node.subNodes atIndexes:set]; 311 | [_tableview insertRowsAtIndexPaths:indexPathes withRowAnimation:animation]; 312 | } 313 | } 314 | } 315 | } 316 | } 317 | 318 | - (id_Nonnull)nodeViewForNode:(id_Nonnull)node{ 319 | NSInteger index; 320 | if (NodeTreeViewStyleBreadcrumbs == self.treeViewStyle) { 321 | index = [self.currentNode.subNodes indexOfObject:node]; 322 | }else{ 323 | index = [self.allNodes indexOfObject:node]; 324 | } 325 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0]; 326 | NodeTreeViewCell *nodeCell = [self.tableview cellForRowAtIndexPath:indexPath]; 327 | return nodeCell.nodeView; 328 | } 329 | 330 | #pragma mark ======== Setters && Getters ======== 331 | - (void)setTreeDelegate:(id)treeDelegate{ 332 | _treeDelegate = treeDelegate; 333 | id delegate = _treeDelegate; 334 | _treeViewDelegateFalg.didSelectNode = [delegate respondsToSelector:@selector(nodeTreeView:didSelectNode:)]; 335 | _treeViewDelegateFalg.indentAtNodelevel = [delegate respondsToSelector:@selector(nodeTreeView:indentAtNodeLevel:)]; 336 | _treeViewDelegateFalg.viewForNode = [delegate respondsToSelector:@selector(nodeTreeView:viewForNode:)]; 337 | 338 | } 339 | - (void)setRefreshPolicy:(NodeTreeRefreshPolicy)refreshPolicy{ 340 | if (refreshPolicy != NodeTreeRefreshPolicyManaul && refreshPolicy != NodeTreeRefreshPolicyAutomic) {//避免外界赋值错误,如果错误会默认变为手动刷新 341 | refreshPolicy = NodeTreeRefreshPolicyManaul; 342 | } 343 | _refreshPolicy = refreshPolicy; 344 | if (NodeTreeRefreshPolicyManaul == _refreshPolicy) {//手动刷新 345 | self.autoRefresh = NO; 346 | }else{//自动刷新 347 | self.autoRefresh = YES; 348 | } 349 | } 350 | 351 | - (UITableView *)tableview{ 352 | if (!_tableview) { 353 | _tableview = [[UITableView alloc]initWithFrame:self.bounds style:UITableViewStylePlain]; 354 | _tableview.delegate = self; 355 | _tableview.dataSource = self; 356 | _tableview.tableFooterView = [[UIView alloc]init]; 357 | _tableview.backgroundColor = [UIColor whiteColor]; 358 | _tableview.scrollEnabled = NO; 359 | } 360 | return _tableview; 361 | } 362 | 363 | - (NSMutableArray> *)allNodes{ 364 | if (!_allNodes) { 365 | _allNodes = [NSMutableArray array]; 366 | } 367 | return _allNodes; 368 | } 369 | @end 370 | 371 | 372 | @implementation NodeTreeViewCell 373 | 374 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{ 375 | if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { 376 | self.selectionStyle = UITableViewCellSelectionStyleNone; 377 | } 378 | return self; 379 | } 380 | 381 | - (void)setNodeView:(id)nodeView{ 382 | [self.contentView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; 383 | _nodeView = nodeView; 384 | UIView *subNodeView = (UIView *)_nodeView; 385 | [self.contentView addSubview:subNodeView]; 386 | } 387 | @end 388 | -------------------------------------------------------------------------------- /TreeNodeStructure/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /TreeNodeStructure/ViewController.m: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // ViewController.m 4 | // TreeNodeStructure 5 | // 6 | // Created by ccSunday on 2018/1/23. 7 | // Copyright © 2018年 ccSunday. All rights reserved. 8 | // 9 | 10 | #import "ViewController.h" 11 | #import "AssistMicros.h" 12 | 13 | @interface ViewController () 14 | /** 15 | tableView 16 | */ 17 | @property (nonatomic, strong) UITableView *tableview; 18 | 19 | @end 20 | 21 | @implementation ViewController 22 | { 23 | NSArray *titleArray; 24 | } 25 | 26 | - (void)viewDidLoad { 27 | [super viewDidLoad]; 28 | // Do any additional setup after loading the view, typically from a nib. 29 | titleArray = @[ 30 | @{ 31 | @"title":@"BreadcrumbStyle-Manaul", 32 | @"class":@"BreadcrumbViewController" 33 | }, 34 | @{ 35 | @"title":@"BreadcrumbStyle-automic", 36 | @"class":@"AutoBreadcrumbViewController" 37 | }, 38 | @{ 39 | @"title":@"ExpansionStyle-Manaul", 40 | @"class":@"ExpansionViewController" 41 | }, 42 | @{ 43 | @"title":@"ExpansionStyle-automic", 44 | @"class":@"AutoExpansionViewController" 45 | } 46 | ]; 47 | [self.view addSubview:self.tableview]; 48 | } 49 | 50 | #pragma mark ======== System Delegate ======== 51 | 52 | #pragma mark UITableViewDataSource && UITableViewDelegate 53 | 54 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ 55 | return 1; 56 | } 57 | 58 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ 59 | return titleArray.count; 60 | } 61 | 62 | 63 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ 64 | static NSString *CellID = @"mainCellID"; 65 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellID]; 66 | if (cell == nil) { 67 | cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellID]; 68 | } 69 | cell.textLabel.text = [titleArray[indexPath.row]objectForKey:@"title"]; 70 | return cell; 71 | } 72 | 73 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ 74 | NSDictionary *item = titleArray[indexPath.row]; 75 | Class cls = NSClassFromString([item objectForKey:@"class"]); 76 | [self presentViewController:[[cls alloc]init] animated:YES completion:nil]; 77 | } 78 | 79 | #pragma mark ======== Custom Delegate ======== 80 | 81 | 82 | #pragma mark ======== Private Methods ======== 83 | 84 | 85 | #pragma mark ======== Setters && Getters ======== 86 | 87 | 88 | - (UITableView *)tableview{ 89 | if (!_tableview) { 90 | _tableview = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, WIDTH, HEIGHT) style:UITableViewStyleGrouped]; 91 | _tableview.delegate = self; 92 | _tableview.dataSource = self; 93 | _tableview.rowHeight = 44; 94 | _tableview.tableFooterView = [[UIView alloc]init]; 95 | } 96 | return _tableview; 97 | } 98 | 99 | - (void)didReceiveMemoryWarning { 100 | [super didReceiveMemoryWarning]; 101 | // Dispose of any resources that can be recreated. 102 | } 103 | @end 104 | -------------------------------------------------------------------------------- /TreeNodeStructure/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TreeNodeStructure 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TreeNodeStructureTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TreeNodeStructureTests/TreeNodeStructureTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TreeNodeStructureTests.m 3 | // TreeNodeStructureTests 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TreeNodeStructureTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation TreeNodeStructureTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /TreeNodeStructureUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TreeNodeStructureUITests/TreeNodeStructureUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TreeNodeStructureUITests.m 3 | // TreeNodeStructureUITests 4 | // 5 | // Created by ccSunday on 2018/1/23. 6 | // Copyright © 2018年 ccSunday. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TreeNodeStructureUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation TreeNodeStructureUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | 27 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | [super tearDown]; 33 | } 34 | 35 | - (void)testExample { 36 | // Use recording to get started writing UI tests. 37 | // Use XCTAssert and related functions to verify your tests produce the correct results. 38 | } 39 | 40 | @end 41 | --------------------------------------------------------------------------------