IOS 多级列表的实现

feng7605 5年前
   <p>在项目开发中,层级列表经常遇到,简单点的二级列表利用UITableView的Header就可以实现,再简单点的三级列表通过对Cell高度进行调整也可以实现三级列表的效果。但遇到多级列表,尤其是层次不明的动态列表就比较麻烦了。</p>    <h2><strong>原理</strong></h2>    <p>层级列表和树形结构比较类似,不过不是二叉树,而是多叉树。每个节点只需要拥有指向父节点和子节点的两个指针,就能形成一颗树。我们将多级列表中每一级对象看作一个node,node拥有两个属性,分别为父节点和子节点的ID。</p>    <p>每棵树有个一个虚拟的root节点,它的ID为rootID,所有节点中凡是父节点ID为rootID的便是第一级,对应树结构中的depth(深度)。这样每一个node对象就都拥有了parentID和childrenID, childrenID为node对象的ID。</p>    <p>我们可以通过rootID查出第一级node,再根据第一级node的childrenID查出下一级,依次类推,确定所有节点的父子关系。同时也可以确定叶子节点和第一级节点,也可称</p>    <p>为根节点。</p>    <h2><strong>效果图</strong></h2>    <p><strong>1.一般多级列表</strong></p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/791fb4b29639281a6666127242d9d751.gif"></p>    <p style="text-align:center">一般多级列表.gif</p>    <p><strong>2.记录节点历史状态的列表</strong></p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c392931d173d5379f5cf91c2b578e144.gif"></p>    <p style="text-align:center">记录节点历史状态.gif</p>    <h2><strong>思路</strong></h2>    <p>1.首先根据 rootID 获取所有第一级节点,并放入UITableView的数据源 dataSourceArr 中,展示初始化列表</p>    <p>2. 展开: 点击节点cell,根据 childrenID 查找下一级nodes,并插入到 dataSourceArr 中currentNode的后面,刷新展示</p>    <p>3. 收拢: 点击以打开节点cell,从 dataSourceArr 的CurrentIndex+1开始,如果该节点的level小于currentNode的level,则移除node,否则停止刷新列表。</p>    <p>4.点击cell为叶子节点则不响应展开或收拢操作,并把节点信息通过返回。</p>    <p>dataSourceArr中是这样的一种符合树层级结构的顺序:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/bdee057bc5e8271b001e64c0dfc1ef3d.png"></p>    <p style="text-align:center">dataSourceArr中顺序.png</p>    <h3><strong>定义节点对象</strong></h3>    <p><img src="https://simg.open-open.com/show/fe0d91d93b32b90bcf783f484ac07e32.png"></p>    <p style="text-align:center">节点对象.png</p>    <h2><strong>遇到问题</strong></h2>    <p><strong>1.局部刷新的问题</strong></p>    <p>每次展开或收拢以后刷新列表,一开始采用</p>    <pre>  <code class="language-objectivec">- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation</code></pre>    <p>但会导致节目有整体闪烁的效果,体验不好。最后考虑采用局部刷新 insertRowsAtIndexPaths 和 deleteRowsAtIndexPaths 。</p>    <p>但在刷新中会报错</p>    <p><em>*</em> Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete row 2 from section 0 which only contains 2 rows before the update'</p>    <p>推测原因是 current Cell在刷新时的numberOfRowsInSection和刷新insert or del的cell时numberOfRowsInSection不一致导致 。然后尝试current cell和其他cell分别刷新,完美刷新。</p>    <pre>  <code class="language-objectivec">[_reloadArray removeAllObjects];      [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];        if (currentNode.isExpand) {          //expand          [self expandNodesForParentID:currentNode.childrenID insertIndex:indexPath.row];          [tableView insertRowsAtIndexPaths:_reloadArray withRowAnimation:UITableViewRowAnimationNone];      }else{          //fold          [self foldNodesForLevel:currentNode.level currentIndex:indexPath.row];           [tableView deleteRowsAtIndexPaths:_reloadArray withRowAnimation:UITableViewRowAnimationNone];      }</code></pre>    <p><strong>2.怎么保存节点历史状态</strong></p>    <p>当文件级层比较多时,有时希望能关掉层级后再打开时还能保留子层级的打开状态。我们可以会给每一个node一个是否展开的属性,当fold时只修改currentNode的expand属性,expand时对子节点序isexpand=YES的进行遍历插入。</p>    <pre>  <code class="language-objectivec">//expand  - (NSUInteger)expandNodesForParentID:(NSString*)parentID insertIndex:(NSUInteger)insertIndex{        for (int i = 0 ; i<_nodes.count;i++) {          YKNodeModel *node = _nodes[i];          if ([node.parentID isEqualToString:parentID]) {              if (!self.isPreservation) {                  node.expand = NO;              }              insertIndex++;              [_tempNodes insertObject:node atIndex:insertIndex];              [_reloadArray addObject:[NSIndexPath indexPathForRow:insertIndex inSection:0]];//need reload nodes                if (node.isExpand) {                 insertIndex = [self expandNodesForParentID:node.childrenID insertIndex:insertIndex];              }          }      }        return insertIndex;  }</code></pre>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/a040ff9e3ae3</p>    <p> </p>