UITableView的使用

UITableView简介
UITableViewCell简介以及重用原理介绍
UITableViewCell的几种循环利用方式介绍
自定义Cell的几种方式(StoryBoard Xib 纯代码等)
UITableView的头部可拉伸效果
数据源同步问题

UITableView简介

在众多移动应用中,能看到各式各样的表格数据,也就是我们今天要说的 UITableView,而且基本都是如下两种样式 UITableViewStylePlain 样式UITableViewStyleGrouped 样式

UITableViewStylePlain
UITableViewStyleGrouped

UITableView的使用过程中有两个非常重要的协议即 UITableViewDataSourceUITableViewDelegate

UITableViewDataSource

tableView需要一个数据源(dataSource)来显示数据,它会向数据源查询一共有多少行数据以及每一行显示什么数据等,没有设置数据源的tableView只是一个空壳


// 可选方法,一共有多少组数据,默认是1
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;

//  每一组有多少行数据  
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

// 每一行显示什么内容,调用时刻:每当有一个cell进入视野范围内就会调用
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;          

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

UITableViewCell简介

UITableView的每一行都是一个UITableViewCell,通过tableView:cellForRowAtIndexPath:方法来初始化每一行,而Cell内部有个默认的子视图contentView,它是Cell所显示内容的父视图,可显示一些辅助指示视图。

辅助指示视图

辅助指示视图的作用是显示一个表示动作的图标,可以通过Cell的accessoryType来显示,默认是UITableViewCellAccessoryNone(不显示辅助指示视图),其他值如下:

UITableViewCellAccessoryDisclosureIndicator:

image

UITableViewCellAccessoryDetailDisclosureButton

image

UITableViewCellAccessoryCheckmark

image

还可以通过cell的accessoryView属性来自定义辅助指示视图,比如往右边放一个开关等;

Cell内部结构

contentView下默认有3个子视图: textLabeldetailTextLabelUIImageView

UITableViewCellStyle:
Cell可以通过该属性来决定使用contentView的哪些子视图,以及这些子视图在contentView中的什么位置

  • UITableViewCellStyleDefault

    image

  • UITableViewCellStyleValue1

    image

  • UITableViewCellStyleValue2

    image

  • UITableViewCellStyleSubtitle

    image

UITableView的常见设置
// 分割线颜色
self.tableView.separatorColor = [UIColor redColor];

// 隐藏分割线
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;

self.tableView.allowsSelection = NO; // 不允许选中

// tableView有数据的时候才需要分割线
 self.tableView.tableFooterView = [[UIView alloc] init];

// 当 cell、  header  、footer 的高度是一样的时候,可以 使用以下操作来统一设置高度。
self.tableView.rowHeight = 40;
self.tableView.sectionHeaderHeight = 44;
self.tableView.sectionFooterHeight = 44;
UITableViewCell的常见设置
// 取消选中的样式(常用) 让当前 cell 按下无反应
cell.selectionStyle = UITableViewCellSelectionStyleNone;

// 设置选中的背景色
UIView *selectedBackgroundView = [[UIView alloc] init];
selectedBackgroundView.backgroundColor = [UIColor redColor];
cell.selectedBackgroundView = selectedBackgroundView;

// 设置默认的背景色
UIView *backgroundView = [[UIView alloc] init];
backgroundView.backgroundColor = [UIColor greenColor];
cell.backgroundView = backgroundView;

// backgroundView的优先级 > backgroundColor
// 设置指示器
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.accessoryView = [[UISwitch alloc] init];

UITableViewCell的重用原理

因为iOS设备的内存有限,如果tableView有成千上万条数据,而没有重用的话,那么就需要成千上万个Cell对象,那此时内存将会被耗尽。

重用原理:

当滑动列表数据时,部分Cell会被移出窗口,此时tableView会将窗口外的Cell放入一个缓存池中等待重用。当tableView要求dataSource返回 Cell时,dataSource会先查看这个缓存池中是否有未使用的 Cell,如果有则会用新的数据来配置这个Cell 然后返回给tableView重新显示到窗口中从而避免创建新对象,节约内存。

注意: 因为每⼀行⽤的不一定是同一种类型的Cell,所以缓存池中也会有很多不同类型的 Cell,那么tableView在重⽤用Cell时可能会得到错误类型的 Cell。此时我们可以使用 reuseIdentifier 来解决这个问题。当tableView 要求dataSource返回Cell时,先通过一个字符串标识到对象池中查找对应类型的Cell对象,如果有就重用,没有就传入这个字符串标识来初始化一个新的Cell对象。

重用池模拟

缓存优化的思路:
(1)先去缓存池中查找是否有满足条件的cell,若有那就直接拿来
(2)若没有,就自己创建一个新的cell
(3)创建cell,并且设置一个唯一的标记
(4)给cell设置数据

cell的循环利用方式1

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 0.重用标识
    // 被static修饰的局部变量:只会初始化一次,在整个程序运行过程中,只有一份内存
    static NSString *ID = @"cell";

    // 1.先根据cell的标识去缓存池中查找可循环利用的cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];

    // 2.如果cell为nil(缓存池找不到对应的cell)
    if (cell == nil) {
    //代码创建
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
    //加载xib中的Cell
        cell = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([XMGDealCell class]) owner:nil options:nil] lastObject];
    }

    // 3.覆盖数据
    cell.textLabel.text = [NSString stringWithFormat:@"testdata - %zd", indexPath.row];

    return cell;
}

cell的循环利用方式2

  • 定义一个全局变量
// 定义重用标识
Static NSString *ID = @"cell";
  • 注册某个标识对应的cell类型
// 在这个方法中注册cell
- (void)viewDidLoad {
    [super viewDidLoad];

    // 注册某个标识对应的cell类型
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:ID];
    
    [self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([XMGDealCell class]) bundle:nil] forCellReuseIdentifier:ID];
}
  • 在数据源方法中返回cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 1.去缓存池中查找cell,如果找不到则根据上面注册的Cell类型创建一个
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID forIndexPath:indexPath];
    
    // 2.覆盖数据
    cell.textLabel.text = [NSString stringWithFormat:@"testdata - %zd", indexPath.row];
    
    return cell;
}

cell的循环利用方式3

  • 在storyboard中设置UITableView的Dynamic Prototypes Cell


    image
  • 设置cell的重用标识


    image
  • 在代码中利用重用标识获取cell


static NSString *ID = @"cell";

// 1.先根据cell的标识去缓存池中查找可循环利用的cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];

// 2.覆盖数据
cell.textLabel.text = [NSString stringWithFormat:@"cell - %zd", indexPath.row];

return cell;
两种重用Cell的区别

UITableView中有两种重用Cell的方法:

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

iOS 6dequeueReusableCellWithIdentifier:dequeueReusableCellWithIdentifier:forIndexPath: 所取代。如此一来,在tableView中创建并添加UITableViewCell对象会变得更为精简而流畅。而且使用dequeueReusableCellWithIdentifier:forIndexPath:一定会返回cell,系统在默认没有cell可复用的时候会自动创建一个新的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.创建一个继承自UITableViewCell的子类,比如XMGDealCell
      image
    • 2.在storyboard中

      • 往cell里面增加需要用到的子控件
        image
      • 设置cell的重用标识
        image
      • 设置cell的class为XMGDealCell
        image
    • 3.在控制器中

      • 利用重用标识找到cell
      • 给cell传递模型数据
        image
    • 4.在XMGDealCell中

      • 将storyboard中的子控件连线到类扩展中
        image
      • 需要提供一个模型属性,重写模型的set方法,在这个方法中设置模型数据到子控件上
        image

        image
  • 二、xib自定义cell

    • 1.创建一个继承自UITableViewCell的子类,比如XMGDealCell
    • 2.创建一个xib文件(文件名建议跟cell的类名一样),比如XMGDealCell.xib
      • 拖拽一个UITableViewCell出来
      • 修改cell的class为XMGDealCell
      • 设置cell的重用标识
      • 往cell中添加需要用到的子控件
    • 3.在控制器中
      • 利用registerNib...方法注册xib文件
      • 利用重用标识找到cell(如果没有注册xib文件,就需要手动去加载xib文件)
      • 给cell传递模型数据
    • 4.在XMGDealCell中
      • 将xib中的子控件连线到类扩展中
      • 需要提供一个模型属性,重写模型的set方法,在这个方法中设置模型数据到子控件上
      • 也可以将创建获得cell的代码封装起来(比如cellWithTableView:方法)
  • 三、代码自定义cell(使用frame)

    • 1.创建一个继承自UITableViewCell的子类,比如XMGDealCell
      • initWithStyle:reuseIdentifier:方法中
        • 添加子控件
        • 设置子控件的初始化属性(比如文字颜色、字体)
      • layoutSubviews方法中设置子控件的frame
      • 需要提供一个模型属性,重写模型的set方法,在这个方法中设置模型数据到子控件
    • 2.在控制器中
      • 利用registerClass...方法注册XMGDealCell类
      • 利用重用标识找到cell(如果没有注册类,就需要手动创建cell)
      • 给cell传递模型数据
      • 也可以将创建获得cell的代码封装起来(比如cellWithTableView:方法)
  • 四、代码自定义cell(使用autolayout)

    • 1.创建一个继承自UITableViewCell的子类,比如XMGDealCell
      • initWithStyle:reuseIdentifier:方法中
        • 添加子控件
        • 添加子控件的约束(建议使用Masonry
        • 设置子控件的初始化属性(比如文字颜色、字体)
      • 需要提供一个模型属性,重写模型的set方法,在这个方法中设置模型数据到子控件
  • 2.在控制器中

    • 利用registerClass...方法注册XMGDealCell类
    • 利用重用标识找到cell(如果没有注册类,就需要手动创建cell)
    • 给cell传递模型数据
    • 也可以将创建获得cell的代码封装起来(比如cellWithTableView:方法)
Cell使用Masonry布局

贴一段UITableViewCell中使用代码结合Masonry来布局的例子:

+ (instancetype)cellWithTableView:(UITableView *)tableView
{
    static NSString *ID = @"deal";
    // 创建cell
    XMGDealCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    
    //如果已经注册此时这里就不用再进行判断了
    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;
    
    // 设置数据
    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的一些使用小细节

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运行项目,发现-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section方法不走,所以页面也华丽丽的变成了一排的cell,通过查看文档和资料,原来是iOS11默认开启self-sizing,把这个属性关掉即可:

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

把上面这几句代码加到初始化tableview的地方即可,其他的设置不用变!加完后,再运行,原来的设置就起效了!

3、工具条跟随键盘变化


方式一:通过设置工具条的约束来实现


- (void)viewDidLoad {
    [super viewDidLoad];    
    // 监听键盘通知
    [[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来实现

#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设置下拉背景色
在开发中为了程序的美观,tableView在下拉刷新的时候经常会让下拉之后的背景色与主色调保持一致,如支付宝我的界面:

那么该如何实现这种效果呢? 不要想当然的以为 只要设置了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的区别:
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];
    
    //根据grayView的大小变换后创建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:通过第二个参数 dx 和第三个参数 dy 重置第一个参数 rect 并且作为结果返回。

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

重置的方式为,首先将 rect 的坐标(origin)按照(dx,dy) 进行平移,然后将 rect 的大小(size) 宽度缩小2倍的 dx,高度缩小2倍的 dy 如果参数为负数 则对应放大,比如:

    UIView *grayView = [[UIView alloc] initWithFrame:CGRectMake(50, 200, 200, 200)];
    grayView.backgroundColor = [UIColor grayColor];
    [self.view addSubview:grayView];
    
    //根据grayView的大小变换后创建redView;
    CGRect rect=CGRectInset(grayView.frame, -10, 10);
    UIView *redView=[[UIView alloc]initWithFrame:rect];
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView];

效果图如下:


5、UILabel结合Autolayout计算高度有时候不精确的问题

image.png

通过上图我们可以看到UILabel在使用Autolayout设置约束的时候,由于多行UILabel不知道自己要显示多少内容,不知道自己的真实尺寸,我们需要为他设置preferredMaxLayoutWidth,告诉它布局时最大的参考宽度。

- (void)awakeFromNib
{
    [super awakeFromNib];
   
    // 为了保证计算出来的数值 跟 真正显示出来的效果 一致,需要设置label每一行文字的最大宽度
    self.contentLabel.preferredMaxLayoutWidth = [UIScreen mainScreen].bounds.size.width - 20;
}

6、区分 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代理方法
/**
 * 只要实现这个方法,左划cell出现删除按钮的功能就有了(默认是删除操作)
 * 用户提交了添加(点击了添加按钮)\删除(点击了删除按钮)操作时会调用
 */
- (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的批量操作


上图是系统自带的批量操作样式:

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

当然系统自带的这种样式可能不符合我们的需求,所以此时我们需要自己自定义批量操作

我们可以在Cell中增加一张打钩的图片,默认是隐藏状态 , 同时 在模型中增加一个属性,用来标志用户的选中状态

/** 状态量标识有无被打钩 */
@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;
    
    // 设置数据
    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];
    
    // 设置打钩控件的显示和隐藏
    self.checkView.hidden = !deal.isChecked;
}

动态改变tableHeaderView高度

如果我们单纯的改变去改变 view 的 frame 是无济于事的,tableView 不会时刻适应它的高度,但是我们可以通过下面两种方式来实现:

方式一:

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

方式二:

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

UITableView的头部可拉伸效果

Demo链接

数据源同步问题

大家都知道,我们一般在主线程中刷新UI,然后在子线程中去加载网络数据和数据解析, 这时候假如我们用户要在点击删除这一操作,而这时候子线程又在加载数据(显然我们是在不同线程对同一资源做操作了),我们怎么保证数据源同步的问题呢?

并发访问,数据拷贝
在主线程中首先拷贝一份数据给子线程,同时在子线程中进行新数据的网络请求与数据解析等,这时候如果在主线程删除某些数据的话,他就记录这条删除操作,在子线程完成各种加载操作后将这条操作与子线程进行同步一下,然后再回到主线程刷新界面

缺点:需要拷贝大量数据,比较消耗内


串行访问
如下图所以,我们首先使用GCD创建一个串行队列,子线程先加入队列完成网络加载操作,如果这时候主线程需要修改数据源,这个操作就要等待子线程完成才去进行(串行执行)

缺点:如果子线程的网络请求速度慢,主线程UI操作等待时间长

UITableView介绍 之 复杂cell的高度计算

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容