UITableView的使用

UITableView簡介
UITableViewCell簡介以及重用原理介紹
UITableViewCell的幾種循環(huán)利用方式介紹
自定義Cell的幾種方式(StoryBoard Xib 純代碼等)
UITableView的頭部可拉伸效果
數(shù)據(jù)源同步問題

UITableView簡介

在眾多移動應(yīng)用中,能看到各式各樣的表格數(shù)據(jù),也就是我們今天要說的 UITableView,而且基本都是如下兩種樣式 UITableViewStylePlain 樣式UITableViewStyleGrouped 樣式

UITableViewStylePlain
UITableViewStyleGrouped

UITableView的使用過程中有兩個非常重要的協(xié)議即 UITableViewDataSourceUITableViewDelegate

UITableViewDataSource

tableView需要一個數(shù)據(jù)源(dataSource)來顯示數(shù)據(jù),它會向數(shù)據(jù)源查詢一共有多少行數(shù)據(jù)以及每一行顯示什么數(shù)據(jù)等,沒有設(shè)置數(shù)據(jù)源的tableView只是一個空殼


// 可選方法,一共有多少組數(shù)據(jù),默認是1
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;

//  每一組有多少行數(shù)據(jù)  
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

// 每一行顯示什么內(nèi)容,調(diào)用時刻:每當(dāng)有一個cell進入視野范圍內(nèi)就會調(diào)用
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;          

UITableViewDelegate中的代理方法很多這里就不在一一列舉了.

UITableViewCell簡介

UITableView的每一行都是一個UITableViewCell,通過tableView:cellForRowAtIndexPath:方法來初始化每一行,而Cell內(nèi)部有個默認的子視圖contentView,它是Cell所顯示內(nèi)容的父視圖,可顯示一些輔助指示視圖。

輔助指示視圖

輔助指示視圖的作用是顯示一個表示動作的圖標(biāo),可以通過Cell的accessoryType來顯示,默認是UITableViewCellAccessoryNone(不顯示輔助指示視圖),其他值如下:

UITableViewCellAccessoryDisclosureIndicator:

image

UITableViewCellAccessoryDetailDisclosureButton

image

UITableViewCellAccessoryCheckmark

image

還可以通過cell的accessoryView屬性來自定義輔助指示視圖,比如往右邊放一個開關(guān)等;

Cell內(nèi)部結(jié)構(gòu)

contentView下默認有3個子視圖: textLabel 、 detailTextLabelUIImageView

UITableViewCellStyle:
Cell可以通過該屬性來決定使用contentView的哪些子視圖,以及這些子視圖在contentView中的什么位置

  • UITableViewCellStyleDefault

    image

  • UITableViewCellStyleValue1

    image

  • UITableViewCellStyleValue2

    image

  • UITableViewCellStyleSubtitle

    image

UITableView的常見設(shè)置
// 分割線顏色
self.tableView.separatorColor = [UIColor redColor];

// 隱藏分割線
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;

self.tableView.allowsSelection = NO; // 不允許選中

// tableView有數(shù)據(jù)的時候才需要分割線
 self.tableView.tableFooterView = [[UIView alloc] init];

// 當(dāng) cell、  header  、footer 的高度是一樣的時候,可以 使用以下操作來統(tǒng)一設(shè)置高度。
self.tableView.rowHeight = 40;
self.tableView.sectionHeaderHeight = 44;
self.tableView.sectionFooterHeight = 44;
UITableViewCell的常見設(shè)置
// 取消選中的樣式(常用) 讓當(dāng)前 cell 按下無反應(yīng)
cell.selectionStyle = UITableViewCellSelectionStyleNone;

// 設(shè)置選中的背景色
UIView *selectedBackgroundView = [[UIView alloc] init];
selectedBackgroundView.backgroundColor = [UIColor redColor];
cell.selectedBackgroundView = selectedBackgroundView;

// 設(shè)置默認的背景色
UIView *backgroundView = [[UIView alloc] init];
backgroundView.backgroundColor = [UIColor greenColor];
cell.backgroundView = backgroundView;

// backgroundView的優(yōu)先級 > backgroundColor
// 設(shè)置指示器
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.accessoryView = [[UISwitch alloc] init];

UITableViewCell的重用原理

因為iOS設(shè)備的內(nèi)存有限,如果tableView有成千上萬條數(shù)據(jù),而沒有重用的話,那么就需要成千上萬個Cell對象,那此時內(nèi)存將會被耗盡。

重用原理:

當(dāng)滑動列表數(shù)據(jù)時,部分Cell會被移出窗口,此時tableView會將窗口外的Cell放入一個緩存池中等待重用。當(dāng)tableView要求dataSource返回 Cell時,dataSource會先查看這個緩存池中是否有未使用的 Cell,如果有則會用新的數(shù)據(jù)來配置這個Cell 然后返回給tableView重新顯示到窗口中從而避免創(chuàng)建新對象,節(jié)約內(nèi)存。

注意: 因為每?行?的不一定是同一種類型的Cell,所以緩存池中也會有很多不同類型的 Cell,那么tableView在重?用Cell時可能會得到錯誤類型的 Cell。此時我們可以使用 reuseIdentifier 來解決這個問題。當(dāng)tableView 要求dataSource返回Cell時,先通過一個字符串標(biāo)識到對象池中查找對應(yīng)類型的Cell對象,如果有就重用,沒有就傳入這個字符串標(biāo)識來初始化一個新的Cell對象。

重用池模擬

緩存優(yōu)化的思路:
(1)先去緩存池中查找是否有滿足條件的cell,若有那就直接拿來
(2)若沒有,就自己創(chuàng)建一個新的cell
(3)創(chuàng)建cell,并且設(shè)置一個唯一的標(biāo)記
(4)給cell設(shè)置數(shù)據(jù)

cell的循環(huán)利用方式1

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 0.重用標(biāo)識
    // 被static修飾的局部變量:只會初始化一次,在整個程序運行過程中,只有一份內(nèi)存
    static NSString *ID = @"cell";

    // 1.先根據(jù)cell的標(biāo)識去緩存池中查找可循環(huán)利用的cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];

    // 2.如果cell為nil(緩存池找不到對應(yīng)的cell)
    if (cell == nil) {
    //代碼創(chuàng)建
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
    //加載xib中的Cell
        cell = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([XMGDealCell class]) owner:nil options:nil] lastObject];
    }

    // 3.覆蓋數(shù)據(jù)
    cell.textLabel.text = [NSString stringWithFormat:@"testdata - %zd", indexPath.row];

    return cell;
}

cell的循環(huán)利用方式2

  • 定義一個全局變量
// 定義重用標(biāo)識
Static NSString *ID = @"cell";
  • 注冊某個標(biāo)識對應(yīng)的cell類型
// 在這個方法中注冊cell
- (void)viewDidLoad {
    [super viewDidLoad];

    // 注冊某個標(biāo)識對應(yīng)的cell類型
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:ID];
    
    [self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([XMGDealCell class]) bundle:nil] forCellReuseIdentifier:ID];
}
  • 在數(shù)據(jù)源方法中返回cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 1.去緩存池中查找cell,如果找不到則根據(jù)上面注冊的Cell類型創(chuàng)建一個
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID forIndexPath:indexPath];
    
    // 2.覆蓋數(shù)據(jù)
    cell.textLabel.text = [NSString stringWithFormat:@"testdata - %zd", indexPath.row];
    
    return cell;
}

cell的循環(huán)利用方式3

  • 在storyboard中設(shè)置UITableView的Dynamic Prototypes Cell


    image
  • 設(shè)置cell的重用標(biāo)識


    image
  • 在代碼中利用重用標(biāo)識獲取cell


static NSString *ID = @"cell";

// 1.先根據(jù)cell的標(biāo)識去緩存池中查找可循環(huán)利用的cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];

// 2.覆蓋數(shù)據(jù)
cell.textLabel.text = [NSString stringWithFormat:@"cell - %zd", indexPath.row];

return cell;
兩種重用Cell的區(qū)別

UITableView中有兩種重用Cell的方法:

- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;  
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0); 

iOS 6dequeueReusableCellWithIdentifier:dequeueReusableCellWithIdentifier:forIndexPath: 所取代。如此一來,在tableView中創(chuàng)建并添加UITableViewCell對象會變得更為精簡而流暢。而且使用dequeueReusableCellWithIdentifier:forIndexPath:一定會返回cell,系統(tǒng)在默認沒有cell可復(fù)用的時候會自動創(chuàng)建一個新的cell出來。

注意: 使用dequeueReusableCellWithIdentifier:forIndexPath:的話,必須和下面的兩個配套方法或者是 StoryBoard 配合起來使用:

   [self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([GSDealCell class]) bundle:nil] forCellReuseIdentifier:cellID];
   //或者
   [self.tableView registerClass:[GSDealCell class] forCellReuseIdentifier:cellID];
    

這樣在tableView:cellForRowAtIndexPath:方法中就可以省掉下面這些代碼:

static NSString *CellIdentifier = @"Cell";  
if (cell == nil)   
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    //或者
    cell = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([GSDealCell class]) owner:nil options:nil] lastObject];  

取而代之的是下面這句代碼:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];  

自定義Cell的步驟

  • 一、storyboard自定義cell

    • 1.創(chuàng)建一個繼承自UITableViewCell的子類,比如XMGDealCell
      image
    • 2.在storyboard中

      • 往cell里面增加需要用到的子控件
        image
      • 設(shè)置cell的重用標(biāo)識
        image
      • 設(shè)置cell的class為XMGDealCell
        image
    • 3.在控制器中

      • 利用重用標(biāo)識找到cell
      • 給cell傳遞模型數(shù)據(jù)
        image
    • 4.在XMGDealCell中

      • 將storyboard中的子控件連線到類擴展中
        image
      • 需要提供一個模型屬性,重寫模型的set方法,在這個方法中設(shè)置模型數(shù)據(jù)到子控件上
        image

        image
  • 二、xib自定義cell

    • 1.創(chuàng)建一個繼承自UITableViewCell的子類,比如XMGDealCell
    • 2.創(chuàng)建一個xib文件(文件名建議跟cell的類名一樣),比如XMGDealCell.xib
      • 拖拽一個UITableViewCell出來
      • 修改cell的class為XMGDealCell
      • 設(shè)置cell的重用標(biāo)識
      • 往cell中添加需要用到的子控件
    • 3.在控制器中
      • 利用registerNib...方法注冊xib文件
      • 利用重用標(biāo)識找到cell(如果沒有注冊xib文件,就需要手動去加載xib文件)
      • 給cell傳遞模型數(shù)據(jù)
    • 4.在XMGDealCell中
      • 將xib中的子控件連線到類擴展中
      • 需要提供一個模型屬性,重寫模型的set方法,在這個方法中設(shè)置模型數(shù)據(jù)到子控件上
      • 也可以將創(chuàng)建獲得cell的代碼封裝起來(比如cellWithTableView:方法)
  • 三、代碼自定義cell(使用frame)

    • 1.創(chuàng)建一個繼承自UITableViewCell的子類,比如XMGDealCell
      • initWithStyle:reuseIdentifier:方法中
        • 添加子控件
        • 設(shè)置子控件的初始化屬性(比如文字顏色、字體)
      • layoutSubviews方法中設(shè)置子控件的frame
      • 需要提供一個模型屬性,重寫模型的set方法,在這個方法中設(shè)置模型數(shù)據(jù)到子控件
    • 2.在控制器中
      • 利用registerClass...方法注冊XMGDealCell類
      • 利用重用標(biāo)識找到cell(如果沒有注冊類,就需要手動創(chuàng)建cell)
      • 給cell傳遞模型數(shù)據(jù)
      • 也可以將創(chuàng)建獲得cell的代碼封裝起來(比如cellWithTableView:方法)
  • 四、代碼自定義cell(使用autolayout)

    • 1.創(chuàng)建一個繼承自UITableViewCell的子類,比如XMGDealCell
      • initWithStyle:reuseIdentifier:方法中
        • 添加子控件
        • 添加子控件的約束(建議使用Masonry
        • 設(shè)置子控件的初始化屬性(比如文字顏色、字體)
      • 需要提供一個模型屬性,重寫模型的set方法,在這個方法中設(shè)置模型數(shù)據(jù)到子控件
  • 2.在控制器中

    • 利用registerClass...方法注冊XMGDealCell類
    • 利用重用標(biāo)識找到cell(如果沒有注冊類,就需要手動創(chuàng)建cell)
    • 給cell傳遞模型數(shù)據(jù)
    • 也可以將創(chuàng)建獲得cell的代碼封裝起來(比如cellWithTableView:方法)
Cell使用Masonry布局

貼一段UITableViewCell中使用代碼結(jié)合Masonry來布局的例子:

+ (instancetype)cellWithTableView:(UITableView *)tableView
{
    static NSString *ID = @"deal";
    // 創(chuàng)建cell
    XMGDealCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    
    //如果已經(jīng)注冊此時這里就不用再進行判斷了
    if (cell == nil) {
        cell = [[XMGDealCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
    }
    return cell;
}

// 1.在initWithStyle:reuseIdentifier:方法中添加子控件
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
        CGFloat margin = 10;
        
        UIImageView *iconView = [[UIImageView alloc] init];
        [self.contentView addSubview:iconView];
        self.iconView = iconView;
        [iconView makeConstraints:^(MASConstraintMaker *make) {
            make.width.equalTo(100);
            make.left.top.offset(margin);
            make.bottom.offset(-margin);
        }];
        
        UILabel *titleLabel = [[UILabel alloc] init];
        [self.contentView addSubview:titleLabel];
        self.titleLabel = titleLabel;
        [titleLabel makeConstraints:^(MASConstraintMaker *make) {
            make.top.equalTo(iconView);
            make.left.equalTo(iconView.right).offset(margin);
            make.right.offset(-margin);
        }];
        
        UILabel *priceLabel = [[UILabel alloc] init];
        priceLabel.textColor = [UIColor orangeColor];
        [self.contentView addSubview:priceLabel];
        self.priceLabel = priceLabel;
        [priceLabel makeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(titleLabel);
            make.bottom.equalTo(iconView);
            make.width.equalTo(70);
        }];
        
        UILabel *buyCountLabel = [[UILabel alloc] init];
        buyCountLabel.textAlignment = NSTextAlignmentRight;
        buyCountLabel.font = [UIFont systemFontOfSize:14];
        buyCountLabel.textColor = [UIColor lightGrayColor];
        [self.contentView addSubview:buyCountLabel];
        self.buyCountLabel = buyCountLabel;
        [buyCountLabel makeConstraints:^(MASConstraintMaker *make) {
            make.bottom.equalTo(priceLabel);
            make.right.equalTo(titleLabel);
            make.left.equalTo(priceLabel.right).offset(margin);
        }];
    }
    return self;
}

// 3.重寫模型的set方法
- (void)setDeal:(XMGDeal *)deal
{
    _deal = deal;
    
    // 設(shè)置數(shù)據(jù)
    self.iconView.image = [UIImage imageNamed:deal.icon];
    self.titleLabel.text = deal.title;
    self.priceLabel.text = [NSString stringWithFormat:@"¥%@", deal.price];
    self.buyCountLabel.text = [NSString stringWithFormat:@"%@人已購買", deal.buyCount];
}

參考鏈接

TableView的一些使用小細節(jié)

1、TableView滾動到頂部的兩種方法

[self.tableView setContentOffset:CGPointMake(0,0) animated:YES];

[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];

2、 iOS11中heightForHeaderInSection 高度無效的問題
更新到iOS11之后,使用XCode9運行項目,發(fā)現(xiàn)-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section方法不走,所以頁面也華麗麗的變成了一排的cell,通過查看文檔和資料,原來是iOS11默認開啟self-sizing,把這個屬性關(guān)掉即可:

self.tableView.estimatedRowHeight = 0;
self.tableView.estimatedSectionHeaderHeight = 0;
self.tableView.estimatedSectionFooterHeight = 0;

把上面這幾句代碼加到初始化tableview的地方即可,其他的設(shè)置不用變!加完后,再運行,原來的設(shè)置就起效了!

3、工具條跟隨鍵盤變化


方式一:通過設(shè)置工具條的約束來實現(xiàn)


- (void)viewDidLoad {
    [super viewDidLoad];    
    // 監(jiān)聽鍵盤通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
}

#pragma mark - 鍵盤處理
- (void)keyboardWillChangeFrame:(NSNotification *)note {
    // 取出鍵盤最終的frame
    CGRect rect = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
    // 取出鍵盤彈出需要花費的時間
    double duration = [note.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    
    // 修改約束
    self.bottomSpacing.constant = [UIScreen mainScreen].bounds.size.height - rect.origin.y;
    [UIView animateWithDuration:duration animations:^{
        [self.view layoutIfNeeded];
    }];
}
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

方式二:拋開控制約束,我們可以直接通過transform來實現(xiàn)

#pragma mark - 鍵盤處理
- (void)keyboardWillChangeFrame:(NSNotification *)note {
    // 取出鍵盤最終的frame
    CGRect rect = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
    // 取出鍵盤彈出需要花費的時間
    double duration = [note.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    // 修改transform
    [UIView animateWithDuration:duration animations:^{
        CGFloat ty = [UIScreen mainScreen].bounds.size.height - rect.origin.y;
        self.view.transform = CGAffineTransformMakeTranslation(0, - ty);
    }];
}

4、tableView設(shè)置下拉背景色
在開發(fā)中為了程序的美觀,tableView在下拉刷新的時候經(jīng)常會讓下拉之后的背景色與主色調(diào)保持一致,如支付寶我的界面:

那么該如何實現(xiàn)這種效果呢? 不要想當(dāng)然的以為 只要設(shè)置了tableView的背景色就可以了哦

    CGRect rect=  CGRectOffset(self.tableView.bounds, 0, -self.tableView.bounds.size.height);
    UIView *bgView = [[UIView alloc] initWithFrame:rect];
    bgView.backgroundColor =  [UIColor blueColor];
    [self.tableView insertSubview:bgView atIndex:0];

這里使用到了CGRectOffset 那么我們就來討論下 CGRectOffsetCGRectInset的區(qū)別:
CGRectOffset

CGRect CGRectOffset(CGRect rect, CGFloat dx, CGFloat dy);

rect 按照(dx,dy)進行平移 沒有縮放

    UIView *grayView = [[UIView alloc] initWithFrame:CGRectMake(50, 200, 200, 200)];
    grayView.backgroundColor = [UIColor grayColor];
    [self.view addSubview:grayView];
    
    //根據(jù)grayView的大小變換后創(chuàng)建redView;
    CGRect rect=CGRectOffset(grayView.frame, 0, -grayView.frame.size.height);
    UIView *redView=[[UIView alloc]initWithFrame:rect];
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView];

效果圖如下:

CGRectInset:通過第二個參數(shù) dx 和第三個參數(shù) dy 重置第一個參數(shù) rect 并且作為結(jié)果返回。

CGRect CGRectInset(CGRect rect, CGFloat dx, CGFloat dy):

重置的方式為,首先將 rect 的坐標(biāo)(origin)按照(dx,dy) 進行平移,然后將 rect 的大?。╯ize) 寬度縮小2倍的 dx,高度縮小2倍的 dy 如果參數(shù)為負數(shù) 則對應(yīng)放大,比如:

    UIView *grayView = [[UIView alloc] initWithFrame:CGRectMake(50, 200, 200, 200)];
    grayView.backgroundColor = [UIColor grayColor];
    [self.view addSubview:grayView];
    
    //根據(jù)grayView的大小變換后創(chuàng)建redView;
    CGRect rect=CGRectInset(grayView.frame, -10, 10);
    UIView *redView=[[UIView alloc]initWithFrame:rect];
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView];

效果圖如下:


5、UILabel結(jié)合Autolayout計算高度有時候不精確的問題

image.png

通過上圖我們可以看到UILabel在使用Autolayout設(shè)置約束的時候,由于多行UILabel不知道自己要顯示多少內(nèi)容,不知道自己的真實尺寸,我們需要為他設(shè)置preferredMaxLayoutWidth,告訴它布局時最大的參考寬度。

- (void)awakeFromNib
{
    [super awakeFromNib];
   
    // 為了保證計算出來的數(shù)值 跟 真正顯示出來的效果 一致,需要設(shè)置label每一行文字的最大寬度
    self.contentLabel.preferredMaxLayoutWidth = [UIScreen mainScreen].bounds.size.width - 20;
}

6、區(qū)分 tableHeaderView和sectionHeaderView

3FBDF798-E177-4F6F-A160-7B8A5AE45082

2CB7F464-0B3C-483F-81EC-71068A1D6CD5

7、局部刷新

   // 局部刷新
    NSIndexPath *path = [NSIndexPath indexPathForRow:row inSection:0];

   //推薦這種做法
    [self.tableView reloadRowsAtIndexPaths:@[path] withRowAnimation:UITableViewRowAnimationBottom];

    // 全部刷新,會重新刷新屏幕上的所有Cell,一般不推薦這么粗暴解決,因為比較浪費性能
    [self.tableView reloadData];

8、tableView編輯模式

    // 讓tableView進入編輯模式
    [self.tableView setEditing:!self.tableView.isEditing animated:YES];

#pragma mark - TableView代理方法
/**
 * 只要實現(xiàn)這個方法,左劃cell出現(xiàn)刪除按鈕的功能就有了(默認是刪除操作)
 * 用戶提交了添加(點擊了添加按鈕)\刪除(點擊了刪除按鈕)操作時會調(diào)用
 */
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete) {  // 點擊了“刪除”
        // 刪除模型
        [self.deals removeObjectAtIndex:indexPath.row];
        
        // 刷新表格
        [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
    } else if (editingStyle == UITableViewCellEditingStyleInsert) { // 點擊了+
        NSLog(@"+++++ %zd", indexPath.row);
    }
}

/**
 * 這個方法決定了tableView進入編輯模式時,每一行的編輯類型:insert(+按鈕)、delete(-按鈕)
 */
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return indexPath.row % 2 == 0? UITableViewCellEditingStyleInsert : UITableViewCellEditingStyleDelete;
}

9、Cell的批量操作


上圖是系統(tǒng)自帶的批量操作樣式:

- (void)viewDidLoad {
    [super viewDidLoad];    
    // 允許在編輯模式進行多選操作
    self.tableView.allowsMultipleSelectionDuringEditing = YES;
}

//讓tableView進入編輯模式
- (IBAction)multiOperation:(id)sender {
    [self.tableView setEditing:!self.tableView.isEditing animated:YES];
}

- (IBAction)remove {
    // 獲得所有被選中的行
    NSArray *indexPaths = [self.tableView indexPathsForSelectedRows];

    NSMutableArray *deletedDeals = [NSMutableArray array];
    for (NSIndexPath *path in indexPaths) {
        [deletedDeals addObject:self.deals[path.row]];
    }
    [self.deals removeObjectsInArray:deletedDeals];
    [self.tableView reloadData];
}

當(dāng)然系統(tǒng)自帶的這種樣式可能不符合我們的需求,所以此時我們需要自己自定義批量操作

我們可以在Cell中增加一張打鉤的圖片,默認是隱藏狀態(tài) , 同時 在模型中增加一個屬性,用來標(biāo)志用戶的選中狀態(tài)

/** 狀態(tài)量標(biāo)識有無被打鉤 */
@property (assign, nonatomic, getter=isChecked) BOOL checked;

在用戶點擊Cell的時候操作checked屬性

#pragma mark - TableView代理方法
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 取消選中這一行
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    
    // 模型的打鉤屬性取反
    XMGDeal *deal = self.deals[indexPath.row];
    deal.checked = !deal.isChecked;
    
    // 刷新表格
    [tableView reloadData];
}

同時在Cell中來操作打鉤圖片的顯示和隱藏即可

- (void)setDeal:(XMGDeal *)deal
{
    _deal = deal;
    
    // 設(shè)置數(shù)據(jù)
    self.iconView.image = [UIImage imageNamed:deal.icon];
    self.titleLabel.text = deal.title;
    self.priceLabel.text = [NSString stringWithFormat:@"¥%@", deal.price];
    self.buyCountLabel.text = [NSString stringWithFormat:@"%@人已購買", deal.buyCount];
    
    // 設(shè)置打鉤控件的顯示和隱藏
    self.checkView.hidden = !deal.isChecked;
}

動態(tài)改變tableHeaderView高度

如果我們單純的改變?nèi)ジ淖?view 的 frame 是無濟于事的,tableView 不會時刻適應(yīng)它的高度,但是我們可以通過下面兩種方式來實現(xiàn):

方式一:

headerView.frame = newFrame;
[self.tableView setTableHeaderView:headerView];

方式二:

self.tableView.tableHeaderView = headerView;
.
.
[self.tableView beginUpdates];
[self.tableView setTableHeaderView:headerView];
[self.tableView endUpdates];

UITableView的頭部可拉伸效果

Demo鏈接

數(shù)據(jù)源同步問題

大家都知道,我們一般在主線程中刷新UI,然后在子線程中去加載網(wǎng)絡(luò)數(shù)據(jù)和數(shù)據(jù)解析, 這時候假如我們用戶要在點擊刪除這一操作,而這時候子線程又在加載數(shù)據(jù)(顯然我們是在不同線程對同一資源做操作了),我們怎么保證數(shù)據(jù)源同步的問題呢?

并發(fā)訪問,數(shù)據(jù)拷貝
在主線程中首先拷貝一份數(shù)據(jù)給子線程,同時在子線程中進行新數(shù)據(jù)的網(wǎng)絡(luò)請求與數(shù)據(jù)解析等,這時候如果在主線程刪除某些數(shù)據(jù)的話,他就記錄這條刪除操作,在子線程完成各種加載操作后將這條操作與子線程進行同步一下,然后再回到主線程刷新界面

缺點:需要拷貝大量數(shù)據(jù),比較消耗內(nèi)


串行訪問
如下圖所以,我們首先使用GCD創(chuàng)建一個串行隊列,子線程先加入隊列完成網(wǎng)絡(luò)加載操作,如果這時候主線程需要修改數(shù)據(jù)源,這個操作就要等待子線程完成才去進行(串行執(zhí)行)

缺點:如果子線程的網(wǎng)絡(luò)請求速度慢,主線程UI操作等待時間長

UITableView介紹 之 復(fù)雜cell的高度計算

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容