如何優雅地解決tableView上有多種cell

nrsf3648 7年前發布 | 18K 次閱讀 iOS開發 移動開發 UITableView

序言

在開發時我們經常遇到一個表中有各種各樣的cell,高度不一,布局不同,這使我們很頭疼,剛接觸iOS的人通常的做法做法通常如下

1、返回cell高度:通過indexPath各種判斷,如果再有某個cell顯示與不顯示,那就更麻煩了

2、返回cell:通過indexPath各種判斷,如果這個cell顯示,則返回,不顯示則。。。

3、heightForHeader、heightForFooter如是

按照上面做,逐個條件判斷,完全可以達到效果

此時,產品說在某個cell下面添加一個cell,該cell在某種情況下顯示,在某種情況下不顯示,上面1、2、3又是一通判斷,此時你是不是想干掉產品,哈哈。。。

歸根結底,上面做法是代碼的維護性不強,下面我們就來解決這個繁瑣的問題,感興趣的朋友可以寫個demo試一下

下面內容依次是cell重用相關、避免tableView卡頓的做法、最后才是如何優雅地解決tableView上有多種cell

代碼都是項目里面的,偷懶直接粘過來了

一、cell的重用相關以及何時在cell中使用代理

第一種(重用)

單元格重用機制

在剛開始創建單元格時,會從隊列中通過標識符尋找未被占用的單元格,若沒有,則會創建新的單元格,剛開始隊列中肯定沒有單元格,一般情況下系統會默認先走固定次數的代理方法創建固定數目的單元格,若滑動過程中,還要出現單元格,這時系統不會再創建單元格,在滑動單元格時肯定有單元格出屏幕,那么出屏幕的會進入隊列,進屏幕的新單元格不會被創建,而是根據標識符去取閑置的單元格,注意標識符必須一樣,不一樣的話還要創建新的單元格 不管怎么樣,滑動中看見的單元格數目再多,其實最終只創建了固定數目的單元格,其余都是重用

+ (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;
}

(1)重用cell時不走awakeFromNib方法

(2)在cell中最好不要創建數組和字典來裝數據再傳到VC中使用,勢必會造成創建多個,跟著數據源model走就會避免此類問題,這樣的好處在于不用再使用傳值方式把值傳到VC中,分兩種情況討論一下

1、非列表形式的tableView,肯定只有一個model,如果VC中需要一個字符串或者布爾值,在model中聲明一個,把在cell中獲取的值賦給model即可,這樣的話直接在VC中使用model即可

2、列表形式的tableView,同樣是在每個小model中聲明一個屬性,在cell中賦值即可,更進一步,如果在VC中需要的是滿足條件的cell上的數據,那么只需要在VC中遍歷裝有小model的數組即可,把滿足條件的小model取出來,,,常用于多選cell

第二種(未重用)

+ (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;
}

這種加載cell方式勢必會造成列表卡頓,因為每次加載cell時都是一個新的cell,這就造成每次加載的控件都是新的,并且每次加載cell都會走awakeFromNib方法,弊端很大

何時使用代理

(1)在從cell向VC中傳值時,只需要按照上面第一種所說的方式即可,不需要使用代理把值傳到VC中,但是對于cell上的點擊事件(button,textField等),最好使用代理去完成了,但是VC與VC之間回傳就需要使用代理或者block了,當然KVC也可以

(2)VC推到下一個VC時,出了push之后的代碼塊,下一個VC并未被釋放,而是為當前導航控制器所持有,在下一個控制器pop時之后,導航控制器才將其釋放,要想不被釋放,把下一個VC作為當前VC的成員變量(strong),由于對下一個VC具有強引用,只有當前VC被釋放了,下一個VC才被釋放,所以每次push到下一個VC時,都是上一次pop的狀態

二、如何解決列表的卡頓現象

1、數據請求下來先計算并緩存好高度
2、圖片異步加載
3、cell重用
4、少用或不用透明圖層,使用不透明視圖
5、盡量不設置圓角

三、如何優雅地解決tableView上有多種cell

1、拿到數據,在model中要做的事情,當然也可以單獨創建一個manager去管理下面代碼

//判斷是必不可少的,先討論各種情況,把要顯示的cellName裝到數組里面,組裝成一個二維數組,第一層就是indexPath.section,第二層就是indexPath.row,這就是整個tableView的架構,因為tableView的組成其實就是一個二維數組,tableView外層是section,section內部是row

- (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"]];
}

2、上面寫好了,數組里面有就顯示,沒有就不顯示,高度就簡單了,下面在model中判斷

//當然也可以在控制器中判斷

  • (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>

    3、根據數組去確定section數

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
      return _detailModel.cellNameArray.count;
    }

    4、根據數組去確定每個section的row數,免去了用indexPath去判斷哪個section有多少個row,哪個cell不顯示時又有多少個row

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
      NSArray *array = _detailModel.cellNameArray[section];
      return array.count;
    }

    5、根據數組去確定該row是哪個cell

    - (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;
      }
    }

    好處

    1、免去了在每個代理方法中寫關于indexPath的重復判斷

    2、在需求變動時,例如刪除一個cell時,在組裝數組時,不把該數組裝進去即可,其它代碼變動不大,甚至不用變

    3、條理清晰,但是該寫的判斷還是要寫的,只不過這些判斷寫在了model里面,后面就不用做判斷了,試想如果不在model里面判斷,tableView每個代理方法中是不是都要做出判斷

     

    來自:http://www.jianshu.com/p/05b4a782b8c6

     

 本文由用戶 nrsf3648 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!