├── .gitignore ├── LICENSE ├── README.md ├── TreeTableVIewWithSwift.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── zhangchao.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── TreeTableVIewWithSwift.xcscheme │ └── xcschememanagement.plist ├── TreeTableVIewWithSwift ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── DataInof.plist ├── Info.plist ├── TreeNode.swift ├── TreeNodeHelper.swift ├── TreeNodeTableViewCell.swift ├── TreeNodeTableViewCell.xib ├── TreeTableView.swift ├── ViewController.swift ├── tree_ec.png └── tree_ex.png └── screenshots └── treetableview-01.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Robert.zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TreeTableViewWithSwift 2 | TreeTableViewWithSwift是用Swift编写的树形结构显示的TableView控件。 3 | 4 | 5 | ## TreeTableViewWithSwift的由来 6 | 在开发企业通讯录的时候需要层级展示。之前开发Android的时候有做过类似的功能,也是通过一些开源的内容进行改造利用。此次,在做ios的同类产品时,调研发现树形结构的控件并不是很多,虽然也有但大多看起来都比较负责,而且都是用OC编写的。介于我的项目是Swift开发的,并且TreeTableView貌似没有人用Swift编写过(也可能是我没找到)。所以打算自己动手写一个,从而丰衣足食。 7 | 8 | 9 | ## TreeTableViewWithSwift简介 10 | >~~开发环境:Swift 2.0,Xcode版本:7.0.1 ,ios 9.0~~ 11 | 升级到 Swift 3.0, Xcode 版本 8.2.1 12 | 13 | 也可以通过简书查看:[简书](http://www.jianshu.com/p/75bcd49f144e) 14 | ### 1、运行效果 15 | 16 | ![image](https://github.com/robertzhang/TreeTableViewWithSwift/raw/master/screenshots/treetableview-01.png) 17 | 18 | ### 2、关键代码的解读 19 | TreeTableViewWithSwift其实是对tableview的扩展。在此之前需要先创建一个TreeNode类用于存储我们的数据 20 | 21 | ``` Swift 22 | public class TreeNode { 23 | 24 | static let NODE_TYPE_G: Int = 0 //表示该节点不是叶子节点 25 | static let NODE_TYPE_N: Int = 1 //表示节点为叶子节点 26 | var type: Int? 27 | var desc: String? // 对于多种类型的内容,需要确定其内容 28 | var id: String? 29 | var pId: String? 30 | var name: String? 31 | var level: Int? 32 | var isExpand: Bool = false 33 | var icon: String? 34 | var children: [TreeNode] = [] 35 | var parent: TreeNode? 36 | 37 | init (desc: String?, id:String? , pId: String? , name: String?) { 38 | self.desc = desc 39 | self.id = id 40 | self.pId = pId 41 | self.name = name 42 | } 43 | 44 | //是否为根节点 45 | func isRoot() -> Bool{ 46 | return parent == nil 47 | } 48 | 49 | //判断父节点是否打开 50 | func isParentExpand() -> Bool { 51 | if parent == nil { 52 | return false 53 | } 54 | return (parent?.isExpand)! 55 | } 56 | 57 | //是否是叶子节点 58 | func isLeaf() -> Bool { 59 | return children.count == 0 60 | } 61 | 62 | //获取level,用于设置节点内容偏左的距离 63 | func getLevel() -> Int { 64 | return parent == nil ? 0 : (parent?.getLevel())!+1 65 | } 66 | 67 | //设置展开 68 | func setExpand(isExpand: Bool) { 69 | self.isExpand = isExpand 70 | if !isExpand { 71 | for (var i=0;i [TreeNode] { 104 | var result: [TreeNode] = [] 105 | var nodes = convetData2Node(groups) 106 | var rootNodes = getRootNodes(nodes) 107 | for item in rootNodes{ 108 | addNode(&result, node: item, defaultExpandLeval: defaultExpandLevel, currentLevel: 1) 109 | } 110 | 111 | return result 112 | } 113 | 114 | 115 | ``` 116 | getSortedNodes是TreeNode的入口方法。调用该方法的时候需要传入一个Array类型的数据集。这个数据集可以是任何你想用来构建树形结构的内容。在这里我虽然只传入了一个groups参数,但其实可以根据需要重构这个方法,传入多个类似groups的参数。例如,当我们需要做企业通讯录的时候,企业通讯录的数据中存在部门集合和用户集合。部门之间有层级关系,用户又属于某个部门。我们可以将部门和用户都转换成TreeNode元数据。这样修改方法可以修改为: 117 | 118 | ``` 119 | func getSortedNodes(groups: NSMutableArray, users: NSMutableArray, defaultExpandLevel: Int) -> [TreeNode] 120 | ``` 121 | 是不是感觉很有意思呢? 122 | 123 | ``` Swift 124 | //过滤出所有可见节点 125 | func filterVisibleNode(nodes: [TreeNode]) -> [TreeNode] { 126 | var result: [TreeNode] = [] 127 | for item in nodes { 128 | if item.isRoot() || item.isParentExpand() { 129 | setNodeIcon(item) 130 | result.append(item) 131 | } 132 | } 133 | return result 134 | } 135 | 136 | //将数据转换成书节点 137 | func convetData2Node(groups: NSMutableArray) -> [TreeNode] { 138 | var nodes: [TreeNode] = [] 139 | 140 | var node: TreeNode 141 | var desc: String? 142 | var id: String? 143 | var pId: String? 144 | var label: String? 145 | var type: Int? 146 | 147 | for item in groups { 148 | desc = item["description"] as? String 149 | id = item["id"] as? String 150 | pId = item["pid"] as? String 151 | label = item["name"] as? String 152 | 153 | node = TreeNode(desc: desc, id: id, pId: pId, name: label) 154 | nodes.append(node) 155 | } 156 | 157 | /** 158 | * 设置Node间,父子关系;让每两个节点都比较一次,即可设置其中的关系 159 | */ 160 | var n: TreeNode 161 | var m: TreeNode 162 | for (var i=0; i [TreeNode] { 189 | var root: [TreeNode] = [] 190 | for item in nodes { 191 | if item.isRoot() { 192 | root.append(item) 193 | } 194 | } 195 | return root 196 | } 197 | 198 | //把一个节点的所有子节点都挂上去 199 | func addNode(inout nodes: [TreeNode], node: TreeNode, defaultExpandLeval: Int, currentLevel: Int) { 200 | nodes.append(node) 201 | if defaultExpandLeval >= currentLevel { 202 | node.setExpand(true) 203 | } 204 | if node.isLeaf() { 205 | return 206 | } 207 | for (var i=0; i 0 { 215 | node.type = TreeNode.NODE_TYPE_G 216 | if node.isExpand { 217 | // 设置icon为向下的箭头 218 | node.icon = "tree_ex.png" 219 | } else if !node.isExpand { 220 | // 设置icon为向右的箭头 221 | node.icon = "tree_ec.png" 222 | } 223 | } else { 224 | node.type = TreeNode.NODE_TYPE_N 225 | } 226 | } 227 | } 228 | ``` 229 | 剩下的代码难度不大,很容易理解。需要多说一句的TreeNode.NODE\_TYPE\_G和TreeNode.NODE\_TYPE\_N是用来告诉TreeNode当前的节点的类型。正如上面提到的企业通讯录,这个两个type就可以用来区分node数据。 230 | 231 | TreeTableView我的重头戏来了。它继承了UITableView, UITableViewDataSource,UITableViewDelegate。 232 | 233 | ``` Swift 234 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 235 | // 通过nib自定义tableviewcell 236 | let nib = UINib(nibName: "TreeNodeTableViewCell", bundle: nil) 237 | tableView.registerNib(nib, forCellReuseIdentifier: NODE_CELL_ID) 238 | 239 | var cell = tableView.dequeueReusableCellWithIdentifier(NODE_CELL_ID) as! TreeNodeTableViewCell 240 | 241 | var node: TreeNode = mNodes![indexPath.row] 242 | 243 | //cell缩进 244 | cell.background.bounds.origin.x = -20.0 * CGFloat(node.getLevel()) 245 | 246 | //代码修改nodeIMG---UIImageView的显示模式. 247 | if node.type == TreeNode.NODE_TYPE_G { 248 | cell.nodeIMG.contentMode = UIViewContentMode.Center 249 | cell.nodeIMG.image = UIImage(named: node.icon!) 250 | } else { 251 | cell.nodeIMG.image = nil 252 | } 253 | 254 | cell.nodeName.text = node.name 255 | cell.nodeDesc.text = node.desc 256 | return cell 257 | } 258 | ``` 259 | tableView:cellForRowAtIndexPath方法中,我们使用了UINib,因为我通过自定义TableViewCell,来填充tableview。这里也使用了cell的复用机制。 260 | 261 | 下面我们来看控制树形结构展开的关键代码 262 | 263 | ``` 264 | func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 265 | var parentNode = mNodes![indexPath.row] 266 | 267 | var startPosition = indexPath.row+1 268 | var endPosition = startPosition 269 | 270 | if parentNode.isLeaf() {// 点击的节点为叶子节点 271 | // do something 272 | } else { 273 | expandOrCollapse(&endPosition, node: parentNode) 274 | mNodes = TreeNodeHelper.sharedInstance.filterVisibleNode(mAllNodes!) //更新可见节点 275 | 276 | //修正indexpath 277 | var indexPathArray :[NSIndexPath] = [] 278 | var tempIndexPath: NSIndexPath? 279 | for (var i = startPosition; i < endPosition ; i++) { 280 | tempIndexPath = NSIndexPath(forRow: i, inSection: 0) 281 | indexPathArray.append(tempIndexPath!) 282 | } 283 | 284 | // 插入和删除节点的动画 285 | if parentNode.isExpand { 286 | self.insertRowsAtIndexPaths(indexPathArray, withRowAnimation: UITableViewRowAnimation.None) 287 | } else { 288 | self.deleteRowsAtIndexPaths(indexPathArray, withRowAnimation: UITableViewRowAnimation.None) 289 | } 290 | //更新被选组节点 291 | self.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None) 292 | 293 | } 294 | 295 | } 296 | 297 | //展开或者关闭某个节点 298 | func expandOrCollapse(inout count: Int, node: TreeNode) { 299 | if node.isExpand { //如果当前节点是开着的,需要关闭节点下的所有子节点 300 | closedChildNode(&count,node: node) 301 | } else { //如果节点是关着的,打开当前节点即可 302 | count += node.children.count 303 | node.setExpand(true) 304 | } 305 | 306 | } 307 | 308 | //关闭某个节点和该节点的所有子节点 309 | func closedChildNode(inout count:Int, node: TreeNode) { 310 | if node.isLeaf() { 311 | return 312 | } 313 | if node.isExpand { 314 | node.isExpand = false 315 | for item in node.children { //关闭子节点 316 | count++ // 计算子节点数加一 317 | closedChildNode(&count, node: item) 318 | } 319 | } 320 | } 321 | 322 | ``` 323 | 我们点击某一个非叶子节点的时候,将该节点的子节点添加到我们的tableView中,并给它们加上动画。这就是我们需要的树形展开视图。首先我们要计算出该节点的子节点数(在关闭节点的时候,还需要计算对应的子节点的子节点的展开节点数),然后获取这些子节点的集合,通过tableview的insertRowsAtIndexPaths和deleteRowsAtIndexPaths方法进行插入节点和删除节点。 324 | 325 | tableview:didSelectRowAtIndexPath还算好理解,关键是expandOrCollapse和closedChildNode方法。 326 | 327 | expandOrCollapse的作用是打开或者关闭点击节点。当操作为打开一个节点的时候,只需要设置该节点为展开,并且计算其子节点数就可以。而关闭一个节点就相对麻烦。因为我们要计算子节点是否是打开的,如果子节点是打开的,那么子节点的子节点的数也要计算进去。可能这里听起来有点绕口,建议运行程序后看着实例进行理解。 328 | 329 | ### 3、鸣谢 330 | 借鉴的资料有: 331 | 332 | * [swift 可展开可收缩的表视图](http://www.jianshu.com/p/706dcc4ccb2f) 333 | 334 | * [Android 打造任意层级树形控件 考验你的数据结构和设计](http://blog.csdn.net/lmj623565791/article/details/40212367) 335 | 336 | 有兴趣的朋友也可以参考以上两篇blog。 337 | 338 | ## License 339 | All source code is licensed under the MIT License. 340 | 341 | 342 | 343 | 344 | -------------------------------------------------------------------------------- /TreeTableVIewWithSwift.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D47BB30F1BDB57AB006D8532 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47BB30E1BDB57AB006D8532 /* AppDelegate.swift */; }; 11 | D47BB3111BDB57AB006D8532 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47BB3101BDB57AB006D8532 /* ViewController.swift */; }; 12 | D47BB3141BDB57AB006D8532 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D47BB3121BDB57AB006D8532 /* Main.storyboard */; }; 13 | D47BB3161BDB57AB006D8532 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D47BB3151BDB57AB006D8532 /* Assets.xcassets */; }; 14 | D47BB3191BDB57AB006D8532 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D47BB3171BDB57AB006D8532 /* LaunchScreen.storyboard */; }; 15 | D47BB3251BDB59B5006D8532 /* TreeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47BB3241BDB59B5006D8532 /* TreeNode.swift */; }; 16 | D47BB3271BDB59F6006D8532 /* TreeNodeHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47BB3261BDB59F6006D8532 /* TreeNodeHelper.swift */; }; 17 | D47BB32C1BDB5F8A006D8532 /* tree_ec.png in Resources */ = {isa = PBXBuildFile; fileRef = D47BB32B1BDB5F8A006D8532 /* tree_ec.png */; }; 18 | D47BB32E1BDB5F94006D8532 /* tree_ex.png in Resources */ = {isa = PBXBuildFile; fileRef = D47BB32D1BDB5F94006D8532 /* tree_ex.png */; }; 19 | D47BB3301BDB610F006D8532 /* TreeTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47BB32F1BDB610F006D8532 /* TreeTableView.swift */; }; 20 | D47BB3331BDB61B7006D8532 /* TreeNodeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47BB3311BDB61B7006D8532 /* TreeNodeTableViewCell.swift */; }; 21 | D47BB3341BDB61B7006D8532 /* TreeNodeTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D47BB3321BDB61B7006D8532 /* TreeNodeTableViewCell.xib */; }; 22 | D47BB3361BDB6958006D8532 /* DataInof.plist in Resources */ = {isa = PBXBuildFile; fileRef = D47BB3351BDB6958006D8532 /* DataInof.plist */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | D47BB30B1BDB57AB006D8532 /* TreeTableVIewWithSwift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TreeTableVIewWithSwift.app; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | D47BB30E1BDB57AB006D8532 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 28 | D47BB3101BDB57AB006D8532 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 29 | D47BB3131BDB57AB006D8532 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 30 | D47BB3151BDB57AB006D8532 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 31 | D47BB3181BDB57AB006D8532 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 32 | D47BB31A1BDB57AB006D8532 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | D47BB3241BDB59B5006D8532 /* TreeNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TreeNode.swift; sourceTree = ""; }; 34 | D47BB3261BDB59F6006D8532 /* TreeNodeHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TreeNodeHelper.swift; sourceTree = ""; }; 35 | D47BB32B1BDB5F8A006D8532 /* tree_ec.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tree_ec.png; sourceTree = ""; }; 36 | D47BB32D1BDB5F94006D8532 /* tree_ex.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tree_ex.png; sourceTree = ""; }; 37 | D47BB32F1BDB610F006D8532 /* TreeTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TreeTableView.swift; sourceTree = ""; }; 38 | D47BB3311BDB61B7006D8532 /* TreeNodeTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TreeNodeTableViewCell.swift; sourceTree = ""; }; 39 | D47BB3321BDB61B7006D8532 /* TreeNodeTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TreeNodeTableViewCell.xib; sourceTree = ""; }; 40 | D47BB3351BDB6958006D8532 /* DataInof.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = DataInof.plist; sourceTree = ""; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | D47BB3081BDB57AB006D8532 /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | ); 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | /* End PBXFrameworksBuildPhase section */ 52 | 53 | /* Begin PBXGroup section */ 54 | D47BB3021BDB57AB006D8532 = { 55 | isa = PBXGroup; 56 | children = ( 57 | D47BB30D1BDB57AB006D8532 /* TreeTableVIewWithSwift */, 58 | D47BB30C1BDB57AB006D8532 /* Products */, 59 | ); 60 | sourceTree = ""; 61 | }; 62 | D47BB30C1BDB57AB006D8532 /* Products */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | D47BB30B1BDB57AB006D8532 /* TreeTableVIewWithSwift.app */, 66 | ); 67 | name = Products; 68 | sourceTree = ""; 69 | }; 70 | D47BB30D1BDB57AB006D8532 /* TreeTableVIewWithSwift */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | D47BB3211BDB592F006D8532 /* Models */, 74 | D47BB3231BDB594E006D8532 /* Views */, 75 | D47BB3221BDB593C006D8532 /* Controllers */, 76 | D47BB32A1BDB5F42006D8532 /* Supporting Files */, 77 | D47BB3121BDB57AB006D8532 /* Main.storyboard */, 78 | D47BB30E1BDB57AB006D8532 /* AppDelegate.swift */, 79 | D47BB3151BDB57AB006D8532 /* Assets.xcassets */, 80 | D47BB3171BDB57AB006D8532 /* LaunchScreen.storyboard */, 81 | D47BB31A1BDB57AB006D8532 /* Info.plist */, 82 | ); 83 | path = TreeTableVIewWithSwift; 84 | sourceTree = ""; 85 | }; 86 | D47BB3211BDB592F006D8532 /* Models */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | D47BB3241BDB59B5006D8532 /* TreeNode.swift */, 90 | D47BB3261BDB59F6006D8532 /* TreeNodeHelper.swift */, 91 | D47BB3351BDB6958006D8532 /* DataInof.plist */, 92 | ); 93 | name = Models; 94 | sourceTree = ""; 95 | }; 96 | D47BB3221BDB593C006D8532 /* Controllers */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | D47BB3101BDB57AB006D8532 /* ViewController.swift */, 100 | ); 101 | name = Controllers; 102 | sourceTree = ""; 103 | }; 104 | D47BB3231BDB594E006D8532 /* Views */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | D47BB32F1BDB610F006D8532 /* TreeTableView.swift */, 108 | D47BB3311BDB61B7006D8532 /* TreeNodeTableViewCell.swift */, 109 | D47BB3321BDB61B7006D8532 /* TreeNodeTableViewCell.xib */, 110 | ); 111 | name = Views; 112 | sourceTree = ""; 113 | }; 114 | D47BB32A1BDB5F42006D8532 /* Supporting Files */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | D47BB32D1BDB5F94006D8532 /* tree_ex.png */, 118 | D47BB32B1BDB5F8A006D8532 /* tree_ec.png */, 119 | ); 120 | name = "Supporting Files"; 121 | sourceTree = ""; 122 | }; 123 | /* End PBXGroup section */ 124 | 125 | /* Begin PBXNativeTarget section */ 126 | D47BB30A1BDB57AB006D8532 /* TreeTableVIewWithSwift */ = { 127 | isa = PBXNativeTarget; 128 | buildConfigurationList = D47BB31D1BDB57AB006D8532 /* Build configuration list for PBXNativeTarget "TreeTableVIewWithSwift" */; 129 | buildPhases = ( 130 | D47BB3071BDB57AB006D8532 /* Sources */, 131 | D47BB3081BDB57AB006D8532 /* Frameworks */, 132 | D47BB3091BDB57AB006D8532 /* Resources */, 133 | ); 134 | buildRules = ( 135 | ); 136 | dependencies = ( 137 | ); 138 | name = TreeTableVIewWithSwift; 139 | productName = TreeTableVIewWithSwift; 140 | productReference = D47BB30B1BDB57AB006D8532 /* TreeTableVIewWithSwift.app */; 141 | productType = "com.apple.product-type.application"; 142 | }; 143 | /* End PBXNativeTarget section */ 144 | 145 | /* Begin PBXProject section */ 146 | D47BB3031BDB57AB006D8532 /* Project object */ = { 147 | isa = PBXProject; 148 | attributes = { 149 | LastUpgradeCheck = 0820; 150 | ORGANIZATIONNAME = robertzhang; 151 | TargetAttributes = { 152 | D47BB30A1BDB57AB006D8532 = { 153 | CreatedOnToolsVersion = 7.0.1; 154 | DevelopmentTeam = E2CNGQLV8B; 155 | LastSwiftMigration = 0820; 156 | }; 157 | }; 158 | }; 159 | buildConfigurationList = D47BB3061BDB57AB006D8532 /* Build configuration list for PBXProject "TreeTableVIewWithSwift" */; 160 | compatibilityVersion = "Xcode 3.2"; 161 | developmentRegion = English; 162 | hasScannedForEncodings = 0; 163 | knownRegions = ( 164 | en, 165 | Base, 166 | ); 167 | mainGroup = D47BB3021BDB57AB006D8532; 168 | productRefGroup = D47BB30C1BDB57AB006D8532 /* Products */; 169 | projectDirPath = ""; 170 | projectRoot = ""; 171 | targets = ( 172 | D47BB30A1BDB57AB006D8532 /* TreeTableVIewWithSwift */, 173 | ); 174 | }; 175 | /* End PBXProject section */ 176 | 177 | /* Begin PBXResourcesBuildPhase section */ 178 | D47BB3091BDB57AB006D8532 /* Resources */ = { 179 | isa = PBXResourcesBuildPhase; 180 | buildActionMask = 2147483647; 181 | files = ( 182 | D47BB3191BDB57AB006D8532 /* LaunchScreen.storyboard in Resources */, 183 | D47BB32E1BDB5F94006D8532 /* tree_ex.png in Resources */, 184 | D47BB3361BDB6958006D8532 /* DataInof.plist in Resources */, 185 | D47BB32C1BDB5F8A006D8532 /* tree_ec.png in Resources */, 186 | D47BB3341BDB61B7006D8532 /* TreeNodeTableViewCell.xib in Resources */, 187 | D47BB3161BDB57AB006D8532 /* Assets.xcassets in Resources */, 188 | D47BB3141BDB57AB006D8532 /* Main.storyboard in Resources */, 189 | ); 190 | runOnlyForDeploymentPostprocessing = 0; 191 | }; 192 | /* End PBXResourcesBuildPhase section */ 193 | 194 | /* Begin PBXSourcesBuildPhase section */ 195 | D47BB3071BDB57AB006D8532 /* Sources */ = { 196 | isa = PBXSourcesBuildPhase; 197 | buildActionMask = 2147483647; 198 | files = ( 199 | D47BB3301BDB610F006D8532 /* TreeTableView.swift in Sources */, 200 | D47BB3331BDB61B7006D8532 /* TreeNodeTableViewCell.swift in Sources */, 201 | D47BB3111BDB57AB006D8532 /* ViewController.swift in Sources */, 202 | D47BB3271BDB59F6006D8532 /* TreeNodeHelper.swift in Sources */, 203 | D47BB30F1BDB57AB006D8532 /* AppDelegate.swift in Sources */, 204 | D47BB3251BDB59B5006D8532 /* TreeNode.swift in Sources */, 205 | ); 206 | runOnlyForDeploymentPostprocessing = 0; 207 | }; 208 | /* End PBXSourcesBuildPhase section */ 209 | 210 | /* Begin PBXVariantGroup section */ 211 | D47BB3121BDB57AB006D8532 /* Main.storyboard */ = { 212 | isa = PBXVariantGroup; 213 | children = ( 214 | D47BB3131BDB57AB006D8532 /* Base */, 215 | ); 216 | name = Main.storyboard; 217 | sourceTree = ""; 218 | }; 219 | D47BB3171BDB57AB006D8532 /* LaunchScreen.storyboard */ = { 220 | isa = PBXVariantGroup; 221 | children = ( 222 | D47BB3181BDB57AB006D8532 /* Base */, 223 | ); 224 | name = LaunchScreen.storyboard; 225 | sourceTree = ""; 226 | }; 227 | /* End PBXVariantGroup section */ 228 | 229 | /* Begin XCBuildConfiguration section */ 230 | D47BB31B1BDB57AB006D8532 /* Debug */ = { 231 | isa = XCBuildConfiguration; 232 | buildSettings = { 233 | ALWAYS_SEARCH_USER_PATHS = NO; 234 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 235 | CLANG_CXX_LIBRARY = "libc++"; 236 | CLANG_ENABLE_MODULES = YES; 237 | CLANG_ENABLE_OBJC_ARC = YES; 238 | CLANG_WARN_BOOL_CONVERSION = YES; 239 | CLANG_WARN_CONSTANT_CONVERSION = YES; 240 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 241 | CLANG_WARN_EMPTY_BODY = YES; 242 | CLANG_WARN_ENUM_CONVERSION = YES; 243 | CLANG_WARN_INFINITE_RECURSION = YES; 244 | CLANG_WARN_INT_CONVERSION = YES; 245 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 246 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 247 | CLANG_WARN_UNREACHABLE_CODE = YES; 248 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 249 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 250 | COPY_PHASE_STRIP = NO; 251 | DEBUG_INFORMATION_FORMAT = dwarf; 252 | ENABLE_STRICT_OBJC_MSGSEND = YES; 253 | ENABLE_TESTABILITY = YES; 254 | GCC_C_LANGUAGE_STANDARD = gnu99; 255 | GCC_DYNAMIC_NO_PIC = NO; 256 | GCC_NO_COMMON_BLOCKS = YES; 257 | GCC_OPTIMIZATION_LEVEL = 0; 258 | GCC_PREPROCESSOR_DEFINITIONS = ( 259 | "DEBUG=1", 260 | "$(inherited)", 261 | ); 262 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 263 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 264 | GCC_WARN_UNDECLARED_SELECTOR = YES; 265 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 266 | GCC_WARN_UNUSED_FUNCTION = YES; 267 | GCC_WARN_UNUSED_VARIABLE = YES; 268 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 269 | MTL_ENABLE_DEBUG_INFO = YES; 270 | ONLY_ACTIVE_ARCH = YES; 271 | SDKROOT = iphoneos; 272 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 273 | }; 274 | name = Debug; 275 | }; 276 | D47BB31C1BDB57AB006D8532 /* Release */ = { 277 | isa = XCBuildConfiguration; 278 | buildSettings = { 279 | ALWAYS_SEARCH_USER_PATHS = NO; 280 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 281 | CLANG_CXX_LIBRARY = "libc++"; 282 | CLANG_ENABLE_MODULES = YES; 283 | CLANG_ENABLE_OBJC_ARC = YES; 284 | CLANG_WARN_BOOL_CONVERSION = YES; 285 | CLANG_WARN_CONSTANT_CONVERSION = YES; 286 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 287 | CLANG_WARN_EMPTY_BODY = YES; 288 | CLANG_WARN_ENUM_CONVERSION = YES; 289 | CLANG_WARN_INFINITE_RECURSION = YES; 290 | CLANG_WARN_INT_CONVERSION = YES; 291 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 292 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 293 | CLANG_WARN_UNREACHABLE_CODE = YES; 294 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 295 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 296 | COPY_PHASE_STRIP = NO; 297 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 298 | ENABLE_NS_ASSERTIONS = NO; 299 | ENABLE_STRICT_OBJC_MSGSEND = YES; 300 | GCC_C_LANGUAGE_STANDARD = gnu99; 301 | GCC_NO_COMMON_BLOCKS = YES; 302 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 303 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 304 | GCC_WARN_UNDECLARED_SELECTOR = YES; 305 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 306 | GCC_WARN_UNUSED_FUNCTION = YES; 307 | GCC_WARN_UNUSED_VARIABLE = YES; 308 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 309 | MTL_ENABLE_DEBUG_INFO = NO; 310 | SDKROOT = iphoneos; 311 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 312 | VALIDATE_PRODUCT = YES; 313 | }; 314 | name = Release; 315 | }; 316 | D47BB31E1BDB57AB006D8532 /* Debug */ = { 317 | isa = XCBuildConfiguration; 318 | buildSettings = { 319 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 320 | INFOPLIST_FILE = TreeTableVIewWithSwift/Info.plist; 321 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 322 | PRODUCT_BUNDLE_IDENTIFIER = robertzhang.TreeTableVIewWithSwift; 323 | PRODUCT_NAME = "$(TARGET_NAME)"; 324 | SWIFT_VERSION = 3.0; 325 | }; 326 | name = Debug; 327 | }; 328 | D47BB31F1BDB57AB006D8532 /* Release */ = { 329 | isa = XCBuildConfiguration; 330 | buildSettings = { 331 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 332 | INFOPLIST_FILE = TreeTableVIewWithSwift/Info.plist; 333 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 334 | PRODUCT_BUNDLE_IDENTIFIER = robertzhang.TreeTableVIewWithSwift; 335 | PRODUCT_NAME = "$(TARGET_NAME)"; 336 | SWIFT_VERSION = 3.0; 337 | }; 338 | name = Release; 339 | }; 340 | /* End XCBuildConfiguration section */ 341 | 342 | /* Begin XCConfigurationList section */ 343 | D47BB3061BDB57AB006D8532 /* Build configuration list for PBXProject "TreeTableVIewWithSwift" */ = { 344 | isa = XCConfigurationList; 345 | buildConfigurations = ( 346 | D47BB31B1BDB57AB006D8532 /* Debug */, 347 | D47BB31C1BDB57AB006D8532 /* Release */, 348 | ); 349 | defaultConfigurationIsVisible = 0; 350 | defaultConfigurationName = Release; 351 | }; 352 | D47BB31D1BDB57AB006D8532 /* Build configuration list for PBXNativeTarget "TreeTableVIewWithSwift" */ = { 353 | isa = XCConfigurationList; 354 | buildConfigurations = ( 355 | D47BB31E1BDB57AB006D8532 /* Debug */, 356 | D47BB31F1BDB57AB006D8532 /* Release */, 357 | ); 358 | defaultConfigurationIsVisible = 0; 359 | defaultConfigurationName = Release; 360 | }; 361 | /* End XCConfigurationList section */ 362 | }; 363 | rootObject = D47BB3031BDB57AB006D8532 /* Project object */; 364 | } 365 | -------------------------------------------------------------------------------- /TreeTableVIewWithSwift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TreeTableVIewWithSwift.xcodeproj/xcuserdata/zhangchao.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /TreeTableVIewWithSwift.xcodeproj/xcuserdata/zhangchao.xcuserdatad/xcschemes/TreeTableVIewWithSwift.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /TreeTableVIewWithSwift.xcodeproj/xcuserdata/zhangchao.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TreeTableVIewWithSwift.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | D47BB30A1BDB57AB006D8532 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /TreeTableVIewWithSwift/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TreeTableVIewWithSwift 4 | // 5 | // Created by Robert Zhang on 15/10/24. 6 | // Copyright © 2015年 robertzhang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // 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. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // 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. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /TreeTableVIewWithSwift/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /TreeTableVIewWithSwift/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 | 27 | 28 | -------------------------------------------------------------------------------- /TreeTableVIewWithSwift/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 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /TreeTableVIewWithSwift/DataInof.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | description 7 | 我的祖国 8 | pid 9 | 0 10 | name 11 | 中国 12 | id 13 | 1 14 | 15 | 16 | id 17 | 2 18 | name 19 | 华北 20 | pid 21 | 1 22 | description 23 | 华北平原 24 | 25 | 26 | id 27 | 3 28 | name 29 | 西北 30 | pid 31 | 1 32 | description 33 | 西北高原 34 | 35 | 36 | id 37 | 4 38 | name 39 | 东北 40 | pid 41 | 1 42 | description 43 | 东北黑土地 44 | 45 | 46 | id 47 | 5 48 | name 49 | 陕西 50 | pid 51 | 3 52 | description 53 | 肉夹馍,凉皮 54 | 55 | 56 | id 57 | 6 58 | name 59 | 新疆 60 | pid 61 | 3 62 | description 63 | 抓饭,烤羊肉 64 | 65 | 66 | id 67 | 7 68 | name 69 | 上海 70 | pid 71 | 2 72 | description 73 | 中国最大的金融中心 74 | 75 | 76 | id 77 | 8 78 | name 79 | 江苏 80 | pid 81 | 2 82 | description 83 | 江南美景 84 | 85 | 86 | id 87 | 9 88 | name 89 | 西安 90 | pid 91 | 5 92 | description 93 | 兵马俑 94 | 95 | 96 | id 97 | 10 98 | name 99 | 杨凌 100 | pid 101 | 5 102 | description 103 | 那里有一个西北农林科技大学 104 | 105 | 106 | id 107 | 11 108 | name 109 | 乌鲁木齐 110 | pid 111 | 6 112 | description 113 | 中国的最西北 114 | 115 | 116 | id 117 | 12 118 | name 119 | 南京 120 | pid 121 | 8 122 | description 123 | 总统府,中山陵 124 | 125 | 126 | id 127 | 13 128 | name 129 | 苏州 130 | pid 131 | 8 132 | description 133 | 丝织品好像还不错 134 | 135 | 136 | id 137 | 14 138 | name 139 | 黑龙江 140 | pid 141 | 4 142 | description 143 | 那里有很多红肠和俄罗斯美女 144 | 145 | 146 | id 147 | 15 148 | name 149 | 吉林 150 | pid 151 | 4 152 | description 153 | 长春一汽好像很厉害的样子 154 | 155 | 156 | id 157 | 16 158 | name 159 | 美国 160 | pid 161 | 0 162 | description 163 | 美利坚合众国 164 | 165 | 166 | id 167 | 17 168 | name 169 | 休斯顿 170 | pid 171 | 16 172 | description 173 | 火箭队 174 | 175 | 176 | id 177 | 18 178 | name 179 | 加拿大 180 | pid 181 | 0 182 | description 183 | 一个不错的移民地 184 | 185 | 186 | id 187 | 19 188 | name 189 | 温哥华 190 | pid 191 | 18 192 | description 193 | 华人很多哦! 194 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /TreeTableVIewWithSwift/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /TreeTableVIewWithSwift/TreeNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TreeNode.swift 3 | // TreeTableVIewWithSwift 4 | // 5 | // Created by Robert Zhang on 15/10/24. 6 | // Copyright © 2015年 robertzhang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | open class TreeNode { 13 | 14 | static let NODE_TYPE_G: Int = 0 //表示该节点不是叶子节点 15 | static let NODE_TYPE_N: Int = 1 //表示节点为叶子节点 16 | var type: Int? 17 | var desc: String? // 对于多种类型的内容,需要确定其内容 18 | var id: String? 19 | var pId: String? 20 | var name: String? 21 | var level: Int? 22 | var isExpand: Bool = false 23 | var icon: String? 24 | var children: [TreeNode] = [] 25 | var parent: TreeNode? 26 | 27 | init (desc: String?, id:String? , pId: String? , name: String?) { 28 | self.desc = desc 29 | self.id = id 30 | self.pId = pId 31 | self.name = name 32 | } 33 | 34 | //是否为根节点 35 | func isRoot() -> Bool{ 36 | return parent == nil 37 | } 38 | 39 | //判断父节点是否打开 40 | func isParentExpand() -> Bool { 41 | if parent == nil { 42 | return false 43 | } 44 | return (parent?.isExpand)! 45 | } 46 | 47 | //是否是叶子节点 48 | func isLeaf() -> Bool { 49 | return children.count == 0 50 | } 51 | 52 | //获取level,用于设置节点内容偏左的距离 53 | func getLevel() -> Int { 54 | return parent == nil ? 0 : (parent?.getLevel())!+1 55 | } 56 | 57 | //设置展开 58 | func setExpand(_ isExpand: Bool) { 59 | self.isExpand = isExpand 60 | if !isExpand { 61 | for i in 0 ..< children.count { 62 | children[i].setExpand(isExpand) 63 | } 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /TreeTableVIewWithSwift/TreeNodeHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TreeNodeHelper.swift 3 | // TreeTableVIewWithSwift 4 | // 5 | // Created by Robert Zhang on 15/10/24. 6 | // Copyright © 2015年 robertzhang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | class TreeNodeHelper { 13 | 14 | // 单例模式 15 | class var sharedInstance: TreeNodeHelper { 16 | struct Static { 17 | static var instance: TreeNodeHelper? = TreeNodeHelper() 18 | 19 | } 20 | return Static.instance! 21 | } 22 | 23 | 24 | //传入普通节点,转换成排序后的Node 25 | func getSortedNodes(_ groups: NSMutableArray, defaultExpandLevel: Int) -> [TreeNode] { 26 | var result: [TreeNode] = [] 27 | let nodes = convetData2Node(groups) 28 | let rootNodes = getRootNodes(nodes) 29 | for item in rootNodes{ 30 | addNode(&result, node: item, defaultExpandLeval: defaultExpandLevel, currentLevel: 1) 31 | } 32 | 33 | return result 34 | } 35 | 36 | //过滤出所有可见节点 37 | func filterVisibleNode(_ nodes: [TreeNode]) -> [TreeNode] { 38 | var result: [TreeNode] = [] 39 | for item in nodes { 40 | if item.isRoot() || item.isParentExpand() { 41 | setNodeIcon(item) 42 | result.append(item) 43 | } 44 | } 45 | return result 46 | } 47 | 48 | //将数据转换成书节点 49 | func convetData2Node(_ groups: NSMutableArray) -> [TreeNode] { 50 | var nodes: [TreeNode] = [] 51 | 52 | var node: TreeNode 53 | var desc: String? 54 | var id: String? 55 | var pId: String? 56 | var label: String? 57 | 58 | for element in groups { 59 | let item = element as? [String:Any] 60 | desc = item?["description"] as? String 61 | id = item?["id"] as? String 62 | pId = item?["pid"] as? String 63 | label = item?["name"] as? String 64 | 65 | 66 | node = TreeNode(desc: desc, id: id, pId: pId, name: label) 67 | nodes.append(node) 68 | } 69 | 70 | /** 71 | * 设置Node间,父子关系;让每两个节点都比较一次,即可设置其中的关系 72 | */ 73 | var n: TreeNode 74 | var m: TreeNode 75 | for i in 0 ..< nodes.count { 76 | n = nodes[i] 77 | 78 | for j in i+1 ..< nodes.count { 79 | m = nodes[j] 80 | if m.pId == n.id { 81 | n.children.append(m) 82 | m.parent = n 83 | } else if n.pId == m.id { 84 | m.children.append(n) 85 | n.parent = m 86 | } 87 | } 88 | } 89 | for item in nodes { 90 | setNodeIcon(item) 91 | } 92 | 93 | return nodes 94 | } 95 | 96 | // 获取根节点集 97 | func getRootNodes(_ nodes: [TreeNode]) -> [TreeNode] { 98 | var root: [TreeNode] = [] 99 | for item in nodes { 100 | if item.isRoot() { 101 | root.append(item) 102 | } 103 | } 104 | return root 105 | } 106 | 107 | //把一个节点的所有子节点都挂上去 108 | func addNode(_ nodes: inout [TreeNode], node: TreeNode, defaultExpandLeval: Int, currentLevel: Int) { 109 | nodes.append(node) 110 | if defaultExpandLeval >= currentLevel { 111 | node.setExpand(true) 112 | } 113 | if node.isLeaf() { 114 | return 115 | } 116 | for i in 0 ..< node.children.count { 117 | addNode(&nodes, node: node.children[i], defaultExpandLeval: defaultExpandLeval, currentLevel: currentLevel+1) 118 | } 119 | } 120 | 121 | // 设置节点图标 122 | func setNodeIcon(_ node: TreeNode) { 123 | if node.children.count > 0 { 124 | node.type = TreeNode.NODE_TYPE_G 125 | if node.isExpand { 126 | // 设置icon为向下的箭头 127 | node.icon = "tree_ex.png" 128 | } else if !node.isExpand { 129 | // 设置icon为向右的箭头 130 | node.icon = "tree_ec.png" 131 | } 132 | } else { 133 | node.type = TreeNode.NODE_TYPE_N 134 | } 135 | } 136 | 137 | 138 | } 139 | -------------------------------------------------------------------------------- /TreeTableVIewWithSwift/TreeNodeTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TreeNodeTableViewCell.swift 3 | // TreeTableVIewWithSwift 4 | // 5 | // Created by 二六三 on 15/10/24. 6 | // Copyright © 2015年 robertzhang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TreeNodeTableViewCell: UITableViewCell { 12 | 13 | 14 | @IBOutlet weak var background: UIView! 15 | @IBOutlet weak var nodeName: UILabel! 16 | @IBOutlet weak var nodeIMG: UIImageView! 17 | @IBOutlet weak var nodeDesc: UILabel! 18 | 19 | override func awakeFromNib() { 20 | super.awakeFromNib() 21 | // Initialization code 22 | } 23 | 24 | override func setSelected(_ selected: Bool, animated: Bool) { 25 | super.setSelected(selected, animated: animated) 26 | 27 | // Configure the view for the selected state 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /TreeTableVIewWithSwift/TreeNodeTableViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /TreeTableVIewWithSwift/TreeTableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TreeTableView.swift 3 | // TreeTableVIewWithSwift 4 | // 5 | // Created by Robert Zhang on 15/10/24. 6 | // Copyright © 2015年 robertzhang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol TreeTableViewCellDelegate: NSObjectProtocol { 12 | func cellClick() //参数还没加,TreeNode表示节点 13 | } 14 | 15 | 16 | class TreeTableView: UITableView, UITableViewDataSource,UITableViewDelegate{ 17 | 18 | var mAllNodes: [TreeNode]? //所有的node 19 | var mNodes: [TreeNode]? //可见的node 20 | 21 | // var treeTableViewCellDelegate: TreeTableViewCellDelegate? 22 | 23 | let NODE_CELL_ID: String = "nodecell" 24 | 25 | init(frame: CGRect, withData data: [TreeNode]) { 26 | super.init(frame: frame, style: UITableViewStyle.plain) 27 | self.delegate = self 28 | self.dataSource = self 29 | mAllNodes = data 30 | mNodes = TreeNodeHelper.sharedInstance.filterVisibleNode(mAllNodes!) 31 | } 32 | 33 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 34 | // 通过nib自定义tableviewcell 35 | let nib = UINib(nibName: "TreeNodeTableViewCell", bundle: nil) 36 | tableView.register(nib, forCellReuseIdentifier: NODE_CELL_ID) 37 | 38 | let cell = tableView.dequeueReusableCell(withIdentifier: NODE_CELL_ID) as! TreeNodeTableViewCell 39 | 40 | let node: TreeNode = mNodes![indexPath.row] 41 | 42 | //cell缩进 43 | cell.background.bounds.origin.x = -20.0 * CGFloat(node.getLevel()) 44 | 45 | //代码修改nodeIMG---UIImageView的显示模式. 46 | if node.type == TreeNode.NODE_TYPE_G { 47 | cell.nodeIMG.contentMode = UIViewContentMode.center 48 | cell.nodeIMG.image = UIImage(named: node.icon!) 49 | } else { 50 | cell.nodeIMG.image = nil 51 | } 52 | 53 | cell.nodeName.text = node.name 54 | cell.nodeDesc.text = node.desc 55 | return cell 56 | } 57 | 58 | required init?(coder aDecoder: NSCoder) { 59 | fatalError("init(coder:) has not been implemented") 60 | } 61 | 62 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 63 | return (mNodes?.count)! 64 | } 65 | 66 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 67 | return 55.0 68 | } 69 | 70 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 71 | let parentNode = mNodes![indexPath.row] 72 | 73 | let startPosition = indexPath.row+1 74 | var endPosition = startPosition 75 | 76 | if parentNode.isLeaf() {// 点击的节点为叶子节点 77 | // do something 78 | } else { 79 | expandOrCollapse(&endPosition, node: parentNode) 80 | mNodes = TreeNodeHelper.sharedInstance.filterVisibleNode(mAllNodes!) //更新可见节点 81 | 82 | //修正indexpath 83 | var indexPathArray :[IndexPath] = [] 84 | var tempIndexPath: IndexPath? 85 | for i in startPosition ..< endPosition { 86 | tempIndexPath = IndexPath(row: i, section: 0) 87 | indexPathArray.append(tempIndexPath!) 88 | } 89 | 90 | // 插入和删除节点的动画 91 | if parentNode.isExpand { 92 | self.insertRows(at: indexPathArray, with: UITableViewRowAnimation.none) 93 | } else { 94 | self.deleteRows(at: indexPathArray, with: UITableViewRowAnimation.none) 95 | } 96 | //更新被选组节点 97 | self.reloadRows(at: [indexPath], with: UITableViewRowAnimation.none) 98 | 99 | } 100 | 101 | } 102 | 103 | //展开或者关闭某个节点 104 | func expandOrCollapse(_ count: inout Int, node: TreeNode) { 105 | if node.isExpand { //如果当前节点是开着的,需要关闭节点下的所有子节点 106 | closedChildNode(&count,node: node) 107 | } else { //如果节点是关着的,打开当前节点即可 108 | count += node.children.count 109 | node.setExpand(true) 110 | } 111 | 112 | } 113 | 114 | //关闭某个节点和该节点的所有子节点 115 | func closedChildNode(_ count:inout Int, node: TreeNode) { 116 | if node.isLeaf() { 117 | return 118 | } 119 | if node.isExpand { 120 | node.isExpand = false 121 | for item in node.children { //关闭子节点 122 | count += 1 // 计算子节点数加一 123 | closedChildNode(&count, node: item) 124 | } 125 | } 126 | } 127 | 128 | } 129 | 130 | -------------------------------------------------------------------------------- /TreeTableVIewWithSwift/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // TreeTableVIewWithSwift 4 | // 5 | // Created by Robert Zhang on 15/10/24. 6 | // Copyright © 2015年 robertzhang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | //获取资源 17 | let plistpath = Bundle.main.path(forResource: "DataInof", ofType: "plist")! 18 | let data = NSMutableArray(contentsOfFile: plistpath) 19 | 20 | // 初始化TreeNode数组 21 | let nodes = TreeNodeHelper.sharedInstance.getSortedNodes(data!, defaultExpandLevel: 0) 22 | 23 | // 初始化自定义的tableView 24 | let tableview: TreeTableView = TreeTableView(frame: CGRect(x: 0, y: 20, width: self.view.frame.width, height: self.view.frame.height-20), withData: nodes) 25 | self.view.addSubview(tableview) 26 | } 27 | 28 | override func didReceiveMemoryWarning() { 29 | super.didReceiveMemoryWarning() 30 | // Dispose of any resources that can be recreated. 31 | } 32 | 33 | 34 | } 35 | 36 | -------------------------------------------------------------------------------- /TreeTableVIewWithSwift/tree_ec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertzhang/TreeTableViewWithSwift/d4fd6911794128e732c84a0783883e28cd9bf85c/TreeTableVIewWithSwift/tree_ec.png -------------------------------------------------------------------------------- /TreeTableVIewWithSwift/tree_ex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertzhang/TreeTableViewWithSwift/d4fd6911794128e732c84a0783883e28cd9bf85c/TreeTableVIewWithSwift/tree_ex.png -------------------------------------------------------------------------------- /screenshots/treetableview-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertzhang/TreeTableViewWithSwift/d4fd6911794128e732c84a0783883e28cd9bf85c/screenshots/treetableview-01.png --------------------------------------------------------------------------------