如何优雅地解决tableView上有多种cell

nrsf3648 8年前
   <h2>序言</h2>    <p>在开发时我们经常遇到一个表中有各种各样的cell,高度不一,布局不同,这使我们很头疼,刚接触iOS的人通常的做法做法通常如下</p>    <p>1、返回cell高度:通过indexPath各种判断,如果再有某个cell显示与不显示,那就更麻烦了</p>    <p>2、返回cell:通过indexPath各种判断,如果这个cell显示,则返回,不显示则。。。</p>    <p>3、heightForHeader、heightForFooter如是</p>    <p>按照上面做,逐个条件判断,完全可以达到效果</p>    <p>此时,产品说在某个cell下面添加一个cell,该cell在某种情况下显示,在某种情况下不显示,上面1、2、3又是一通判断,此时你是不是想干掉产品,哈哈。。。</p>    <p>归根结底,上面做法是代码的维护性不强,下面我们就来解决这个繁琐的问题,感兴趣的朋友可以写个demo试一下</p>    <p>下面内容依次是cell重用相关、避免tableView卡顿的做法、最后才是如何优雅地解决tableView上有多种cell</p>    <p>代码都是项目里面的,偷懒直接粘过来了</p>    <h2>一、cell的重用相关以及何时在cell中使用代理</h2>    <p>第一种(重用)</p>    <p>单元格重用机制</p>    <p>在刚开始创建单元格时,会从队列中通过标识符寻找未被占用的单元格,若没有,则会创建新的单元格,刚开始队列中肯定没有单元格,一般情况下系统会默认先走固定次数的代理方法创建固定数目的单元格,若滑动过程中,还要出现单元格,这时系统不会再创建单元格,在滑动单元格时肯定有单元格出屏幕,那么出屏幕的会进入队列,进屏幕的新单元格不会被创建,而是根据标识符去取闲置的单元格,注意标识符必须一样,不一样的话还要创建新的单元格 不管怎么样,滑动中看见的单元格数目再多,其实最终只创建了固定数目的单元格,其余都是重用</p>    <pre>  <code class="language-objectivec">+ (JSSupplyOrderCell *)cellWithTableView:(UITableView *)tableView{      static NSString *identifier = @"JSSupplyOrderCell";      JSSupplyOrderCell *cell=(JSSupplyOrderCell *)[tableView dequeueReusableCellWithIdentifier:identifier];      if(cell == nil) {          UINib *nib = [UINib nibWithNibName:identifier bundle:nil];          [tableView registerNib:nib forCellReuseIdentifier:identifier];          cell = (JSSupplyOrderCell *)[tableView dequeueReusableCellWithIdentifier:identifier];      }      cell.selectionStyle = UITableViewCellSelectionStyleNone;      return cell;  }</code></pre>    <p>(1)重用cell时不走awakeFromNib方法</p>    <p>(2)在cell中最好不要创建数组和字典来装数据再传到VC中使用,势必会造成创建多个,跟着数据源model走就会避免此类问题,这样的好处在于不用再使用传值方式把值传到VC中,分两种情况讨论一下</p>    <p>1、非列表形式的tableView,肯定只有一个model,如果VC中需要一个字符串或者布尔值,在model中声明一个,把在cell中获取的值赋给model即可,这样的话直接在VC中使用model即可</p>    <p>2、列表形式的tableView,同样是在每个小model中声明一个属性,在cell中赋值即可,更进一步,如果在VC中需要的是满足条件的cell上的数据,那么只需要在VC中遍历装有小model的数组即可,把满足条件的小model取出来,,,常用于多选cell</p>    <p>第二种(未重用)</p>    <pre>  <code class="language-objectivec">+ (JSExpressPriceCell *)cellWithTableView:(UITableView *)tableView{      static NSString *identifier = @"JSExpressPriceCell";      JSExpressPriceCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];      if (cell == nil) {          cell = [[NSBundle mainBundle] loadNibNamed:@"JSExpressPriceCell" owner:nil options:nil][0];          cell.selectionStyle = UITableViewCellSelectionStyleNone;      }      return cell;  }</code></pre>    <p>这种加载cell方式势必会造成列表卡顿,因为每次加载cell时都是一个新的cell,这就造成每次加载的控件都是新的,并且每次加载cell都会走awakeFromNib方法,弊端很大</p>    <p>何时使用代理</p>    <p>(1)在从cell向VC中传值时,只需要按照上面第一种所说的方式即可,不需要使用代理把值传到VC中,但是对于cell上的点击事件(button,textField等),最好使用代理去完成了,但是VC与VC之间回传就需要使用代理或者block了,当然KVC也可以</p>    <p>(2)VC推到下一个VC时,出了push之后的代码块,下一个VC并未被释放,而是为当前导航控制器所持有,在下一个控制器pop时之后,导航控制器才将其释放,要想不被释放,把下一个VC作为当前VC的成员变量(strong),由于对下一个VC具有强引用,只有当前VC被释放了,下一个VC才被释放,所以每次push到下一个VC时,都是上一次pop的状态</p>    <h2>二、如何解决列表的卡顿现象</h2>    <pre>  <code class="language-objectivec">1、数据请求下来先计算并缓存好高度  2、图片异步加载  3、cell重用  4、少用或不用透明图层,使用不透明视图  5、尽量不设置圆角</code></pre>    <h2>三、如何优雅地解决tableView上有多种cell</h2>    <p>1、拿到数据,在model中要做的事情,当然也可以单独创建一个manager去管理下面代码</p>    <p>//判断是必不可少的,先讨论各种情况,把要显示的cellName装到数组里面,组装成一个二维数组,第一层就是indexPath.section,第二层就是indexPath.row,这就是整个tableView的架构,因为tableView的组成其实就是一个二维数组,tableView外层是section,section内部是row</p>    <pre>  <code class="language-objectivec">- (void)getSupplyOrderDetailCellArray{      //_cellNameArray是model的属性,注意add的对象是数组还是字符串      _cellNameArray = [NSMutableArray array];      //第一组      [_cellNameArray addObject:@[@"JSRecieveGoodsCell"]];      //第二组      NSMutableArray *array = [NSMutableArray array];      [array addObject:@"JSSupplyOrderDetailCompanyCell"];      //如果没有规格则该组只有三个cell,否则更多      if (self.spec_info.count == 0) {          [array addObject:@"JSSupplyOrderDetailCell"];      }else{          for (int i = 0; i < self.spec_info.count; i++) {              [array addObject:@"JSSupplyOrderDetailCell"];          }      }      [array addObject:@"JSSupplyOrderDetailMoneyCell"];      [_cellNameArray addObject:array];      //第三组      [_cellNameArray addObject:@[@"JSSupplyOrderDetailExpressPriceCell",@"JSSupplyOrderDetailMessageCell"]];      //第四组      if (self.change_amount.floatValue != 0) {          [_cellNameArray addObject:@[@"JSSupplyOrderDetailModifyPriceCell",@"JSSupplyOrderDetailTotalMoneyCell"]];      }else{          [_cellNameArray addObject:@[@"JSSupplyOrderDetailTotalMoneyCell"]];      }      //第五组      if (self.order_status.integerValue > 1 && self.order_status.integerValue < 5) {          [_cellNameArray addObject:@[@"JSSupplyOrderDetailWatchLogisticsCell"]];      }else if (self.order_status.integerValue == 1 && self.jushi_delivery_status.integerValue == 1){          [_cellNameArray addObject:@[@"JSSupplyOrderDetailWatchLogisticsCell"]];      }      //第六组(账期和月结不会同时存在)      if (self.order_status.integerValue >= 0 && self.order_status.integerValue < 5) {          //只要是确认了账期,在订单状态正常情况下都可以查看账期          if (self.account_period_status.integerValue == 3) {              [_cellNameArray addObject:@[@"JSSupplyOrderDetailWatchPaymentCell"]];          }      }      if (self.repay_time.length > 0) {          [_cellNameArray addObject:@[@"JSSupplyOrderDetailMonthPayCell"]];      }      //第七组      [_cellNameArray addObject:@[@"JSSupplyOrderDetailInfoCell"]];  }</code></pre>    <p>2、上面写好了,数组里面有就显示,没有就不显示,高度就简单了,下面在model中判断</p>    <pre>  <code class="language-objectivec">//当然也可以在控制器中判断  - (CGFloat)getSupplyOrderDetailCellHeight:(NSInteger)section row:(NSInteger)row{      NSArray *array = _cellNameArray[section];      NSString *cellName = array[row];      if ([cellName isEqualToString:@"JSRecieveGoodsCell"]) {          return 100.0f;      }else if ([cellName isEqualToString:@"JSSupplyOrderDetailCell"]){          return 100.0f;      }else if ([cellName isEqualToString:@"JSSupplyOrderDetailInfoCell"]){          return _order_content.count * 21 + 60 + 5;      }else{          return 40.0f;      }  }</code></pre>    <p>3、根据数组去确定section数</p>    <pre>  <code class="language-objectivec">- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{      return _detailModel.cellNameArray.count;  }</code></pre>    <p>4、根据数组去确定每个section的row数,免去了用indexPath去判断哪个section有多少个row,哪个cell不显示时又有多少个row</p>    <pre>  <code class="language-objectivec">- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{      NSArray *array = _detailModel.cellNameArray[section];      return array.count;  }</code></pre>    <p>5、根据数组去确定该row是哪个cell</p>    <pre>  <code class="language-objectivec">- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{      NSArray *array = _detailModel.cellNameArray[indexPath.section];      NSString *cellName = array[indexPath.row];      if ([cellName isEqualToString:@"JSRecieveGoodsCell"]) {          JSRecieveGoodsCell *cell = [JSRecieveGoodsCell cellWithTableView:tableView];          cell.detailModel = _detailModel;          return cell;      }else if ([cellName isEqualToString:@"JSSupplyOrderDetailCompanyCell"]){          JSSupplyOrderDetailCompanyCell *cell = [JSSupplyOrderDetailCompanyCell cellWithTableView:tableView];          cell.detailModel = _detailModel;          return cell;      }else if ([cellName isEqualToString:@"JSSupplyOrderDetailCell"]){          JSSupplyOrderDetailCell *cell = [JSSupplyOrderDetailCell cellWithTableView:tableView];          if (_detailModel.spec_info.count > 0) {              JSSupplyOrderDetailItemModel *model = _detailModel.spec_info[indexPath.row - 1];              cell.itemModel = model;          }          cell.detailModel = _detailModel;          return cell;      }else if ([cellName isEqualToString:@"JSSupplyOrderDetailMoneyCell"]){          JSSupplyOrderDetailMoneyCell *cell = [JSSupplyOrderDetailMoneyCell cellWithTableView:tableView];          cell.detailModel = _detailModel;          return cell;      }else if ([cellName isEqualToString:@"JSSupplyOrderDetailWatchLogisticsCell"]){          JSSupplyOrderDetailWatchLogisticsCell *cell = [JSSupplyOrderDetailWatchLogisticsCell cellWithTableView:tableView];          cell.detailModel = _detailModel;          return cell;      }else if ([cellName isEqualToString:@"JSSupplyOrderDetailExpressPriceCell"]){          JSSupplyOrderDetailExpressPriceCell *cell = [JSSupplyOrderDetailExpressPriceCell cellWithTableView:tableView];          cell.detailModel = _detailModel;          return cell;      }else if ([cellName isEqualToString:@"JSSupplyOrderDetailMessageCell"]){          JSSupplyOrderDetailMessageCell *cell = [JSSupplyOrderDetailMessageCell cellWithTableView:tableView];          cell.detailModel = _detailModel;          return cell;      }else if ([cellName isEqualToString:@"JSSupplyOrderDetailModifyPriceCell"]){          JSSupplyOrderDetailModifyPriceCell *cell = [JSSupplyOrderDetailModifyPriceCell cellWithTableView:tableView];          cell.detailModel = _detailModel;          return cell;      }else if ([cellName isEqualToString:@"JSSupplyOrderDetailTotalMoneyCell"]){          JSSupplyOrderDetailTotalMoneyCell *cell = [JSSupplyOrderDetailTotalMoneyCell cellWithTableView:tableView];          cell.detailModel = _detailModel;          return cell;      }else if ([cellName isEqualToString:@"JSSupplyOrderDetailWatchPaymentCell"]){          JSSupplyOrderDetailWatchPaymentCell *cell = [JSSupplyOrderDetailWatchPaymentCell cellWithTableView:tableView];          cell.detailModel = _detailModel;          return cell;      }else if ([cellName isEqualToString:@"JSSupplyOrderDetailMonthPayCell"]){          JSSupplyOrderDetailMonthPayCell *cell = [JSSupplyOrderDetailMonthPayCell cellWithTableView:tableView];          cell.detailModel = _detailModel;          return cell;      }else{          JSSupplyOrderDetailInfoCell *cell = [JSSupplyOrderDetailInfoCell cellWithTableView:tableView];          cell.detailModel = _detailModel;          return cell;      }  }</code></pre>    <p>好处</p>    <p>1、免去了在每个代理方法中写关于indexPath的重复判断</p>    <p>2、在需求变动时,例如删除一个cell时,在组装数组时,不把该数组装进去即可,其它代码变动不大,甚至不用变</p>    <p>3、条理清晰,但是该写的判断还是要写的,只不过这些判断写在了model里面,后面就不用做判断了,试想如果不在model里面判断,tableView每个代理方法中是不是都要做出判断</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/05b4a782b8c6</p>    <p> </p>