UITableView 及UITableViewCell 简述

921 阅读8分钟

作为在开发中最常用的一个控件,UITableView也是在整个UIKit中比较复杂的一个,我们需要记的东西也特别多.另外苹果官方为我们提供了UITableViewController这个类,但是在这里我们不使用它.而是在UIViewController上面添加一个UITableView.

UITableView

UITableView继承于UIScrollView,当需要展示的数据量很多的时候,它是可以滚动显示的.

UITableView

表视图的每一行都是由单元格(UITableViewCell)表示的.当我们要对数据分组显示时,苹果为我们提供了两种基本样式的显示,一种是分组样式,一种则为简单样式.

样式

创建UITableView

我们初始化一个UITableView,并指定样式,然后进行其相关属性的设置,最后将他添加到控制器上.

	// 1.创建tableView(表视图)并初始化,初始化的时候给一个样式
    UITableView *tableView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:UITableViewStyleGrouped];
    
    // 2.设置属性
    // 设置分割线的颜色
    tableView.separatorColor = [UIColor redColor];
    // 设置分割线的风格
    tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
    // 设置行高
    tableView.rowHeight = 100;
    
    // 设置tableView的tableHeaderView
    UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 414, 200)];
    headerView.backgroundColor = [UIColor cyanColor];
    tableView.tableHeaderView = headerView;
    
    // 设置tableView的tableFooterView(取消下面多余的线)
    UIView *footerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 20)];
    footerView.backgroundColor = [UIColor magentaColor];
    tableView.tableFooterView = footerView;
    
    // 设置索引条
    
    tableView.sectionIndexColor = [UIColor blackColor];
    // 背景颜色
    tableView.sectionIndexBackgroundColor = [UIColor clearColor];
    //
    tableView.sectionIndexTrackingBackgroundColor = [UIColor lightGrayColor];
    
    
    // 3.添加到视图
    [self.view addSubview:tableView];
    

在上面的代码中,我们对UITableView的分割线和行高,一起头尾视图,索引条这几个属性进行了相关设置,对于其他的属性,读者可以自己根据兴趣尝试设置.

关于UITableViewCell的相关

在UITableView中用于展示数据的主要就是UITableViewCell.在这里首相我想先说一下关于UITableViewCell的重用.

UITableViewCell的重用

如果我们想利用UITableView展示几条数据的话,我们可以依次创建这些数量的行来展示这些数据,但是我们如果有10000行甚至更多的数据要用来向用户展示呢?如果反复创建是十分消耗内存的.

这样我们就自然的想到,为什么要创建这么多次cell呢?我们为什么不将创建好的cell保存起来,放在一个队列中重用呢?这就是UITableViewCell的重用机制.当我们有10000条数据需要展示的时候,我们使用这个机制创建的行可能仅需要10个,这样就大大节约了内存.

UITableViewCell的创建

我们可以从官方的API中看到UITableViewCell的初始化方法.

![UITableViewCell的初始化方法]://upload-images.jianshu.io/upload_images/1230517-db6401da06482690.png)

在创建Cell的时候同样需要指定一个样式,并且设置一个标识.关于样式苹果给定了四个样式

Cell样式

读者可以一一试验,着这里就不做详细的讲解.

    if (!cell) {
        // 若重用池里面没有,则去创建identifier标识符的cell
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier];
    }

实际上,上述的创建UITableViewCell的方法是在iOS6之前的老方法.

在iOS6之后我们只需要注册在重用池中注册一下cell的重用标识符.在注册cell之前我们必须为cell设置重用标识符,这个标识符必须唯一.我们通常声明为静态字符串,我们不需要管理字符串的内存,也不需要对其进行释放.

// 声明重用标识符
static NSString *identifier = @"cellReuse";
// 注册(iOS6 之后的写法)
    [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:identifier];

然后在UITableViewDataSource数据源方法中创建就可以了

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    // 先去重用池里面取带有identifier重用标识符的cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];    
    return cell;
}

NSIndexPath

NSIndexPath代表UITableView的索引.由于我们创建的cell是重用的,这样一来我们便没法精确的确定这个行.出于此,系统为我们提供了一个标示行的类,就是NSIndexPath

NSIndexPath的常用属性有:

row表示分区中的行的索引,section标示分区的索引.通过这两个属性我们就可以找到想要的行.

关于自定义cell

关于UITableViewCell我们是可以根据自己的需要自定义的,我们只需要重新创建一个类继承自UITableViewCell,在其中设置成需要的样式.然后在注册和创建的时候使用自定义的cell就可以了,在这里我们假设我们创建一个MyTableViewCell,并且设置了重用标识符,那么注册和创建的方式如下.

注册
[self.tableView registerClass:[MyTableViewCell class] forCellReuseIdentifier:identifier];
创建
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];

UITableViewDelegate和UITableViewDataSource

上面是对UITableView的整体结构介绍,如果我们要创建表视图展示数据,我们必须要为表视图设置代理和数据源,也就是UITableViewDelegate和UITableViewDataSource

先看数据源代理,数据源顾名思义,就是为表视图提供相关的数据.

数据源中必须实现的方法

我们看到API中有两个必须要实现的方法

//分区的个数,也就设置这个表分成几组(section)
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

//创建或者重用cell的代理方法
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

另外我们可以对其每个分区的行数(row)进行设置,还可以设置其头尾(titleForHeaderInSection和titleForFooterInSection).

相对这些我跟想跟大家交流的是一些有趣的方法

编辑UITableView

使页面处于可编辑状态

// 第一步: 使页面处于可编辑状态
- (void)edit:(UINavigationController *)sender{
    
    // 设置当前页面可以被编辑
    // 当点击编辑的时候,页面应该处于可编辑状态,并且按钮文字变成"完成"
    if ([sender.title isEqualToString:@"编辑"]) {
        sender.title = @"完成";
        [_tableView setEditing:YES animated:YES];
        
    }else{
        // 当点击完成时,应该让当前页面处于不可编辑状态,并且按钮文字显示为"编辑"
        sender.title = @"编辑";
        [_tableView setEditing:NO animated:YES];
        
    }
    
}

指定哪些行可以被编辑

// 指定哪些行可以被编辑
-(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath{
    
    if (indexPath.section == 2) {
        return NO;
    }
    
        return YES;
    
}

根据路径指定编辑的样式

// 根据路径指定编辑的样式
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
    if (indexPath.section == 5) {
        return UITableViewCellEditingStyleInsert;
    }
        return UITableViewCellEditingStyleDelete;
}

根据编辑风格完成编辑(先操作数据,在更新UI)

// 根据编辑风格完成编辑(先操作数据,在更新UI)
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
    // 删除
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        
        // 找到对应得分组
        NSMutableArray *array = _dataDictionary[_dataArray[indexPath.section]];
        // 当当前分组只有一个人的时候,删除元素之后,对应的分组也应该被删掉
        if (array.count == 1) {
            // 删除大字典里面的该分组
            [_dataDictionary removeObjectForKey:_dataArray[indexPath.section]];
            // 删除掉数组里面对应的key
            [_dataArray removeObjectAtIndex:indexPath.section];
            // 删除UI
            NSIndexSet *set = [NSIndexSet indexSetWithIndex:indexPath.section];
            
            [tableView deleteSections:set withRowAnimation:UITableViewRowAnimationFade];
            
            
        }else{
            
            // 删除数据
            [array removeObjectAtIndex:indexPath.row];
            // 删除对应的cell  更新UI
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
            

        }
        
    }else{
        // 增加
        NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:@"悟空",@"name",@"男",@"age",@"八戒",@"hobby",@"18833336666",@"phoneNumber",12,@"age",@"八戒.png",@"picture", nil];
        // 找到对应的分组
        NSMutableArray *array = _dataDictionary[_dataArray[indexPath.section]];
        // 添加元素
        [array insertObject:dic atIndex:indexPath.row+1];
        // 增加 更新UI
        // <1 定义一个新路径
        NSIndexPath *newPath = [NSIndexPath indexPathForRow:indexPath.row+1 inSection:indexPath.section];
        [_tableView insertRowsAtIndexPaths:@[newPath] withRowAnimation:UITableViewRowAnimationTop];
    }
    
}

移动UITableViewCell

使页面处于可编辑状态

// 第一步 使页面处于可编辑状态
-(void)setEditing:(BOOL)editing animated:(BOOL)animated{
    
    [super setEditing:editing animated:animated];
    
    [_tableView setEditing:editing animated:animated];
    
    self.navigationItem.rightBarButtonItem.title = editing? @"完成":@"编辑";
}

指定tableView哪些行可以被移动

// 第二步 指定tableView哪些行可以被移动
-(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath{
    return YES;
}

移动完成

// 第三步 移动完成
-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{
    // 获取移动的数据
    NSMutableArray *array = _dataDictionary[_dataArray[sourceIndexPath.section]];
    // 获取移动的对象
    NSDictionary *dic = [array objectAtIndex:sourceIndexPath.row];
    // 先删除
    [array removeObjectAtIndex:sourceIndexPath.row];
    // 再添加
    [array insertObject:dic atIndex:destinationIndexPath.row];
}

检测跨区移动

// 检测跨区移动
-(NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath{
    // 如果就在一个分区,则允许任意移动
    if (sourceIndexPath.section == proposedDestinationIndexPath.section) {
        return proposedDestinationIndexPath;
    }
    // 否则原路滚回去
    return sourceIndexPath;
}

常用的方法还有

// 快速索引
- (nullable NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView;

UITableViewDelegate中常用方法

// 设置行高
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
// 设置分区的header高度
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
// 设置分区的footer高度
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;
// 设置分区headerView的视图(设置之后,与titleForHeader方法不共存且设置的高度没有用,想要确定,headerView的高度,必须重写heightForHeaderInSection方法)
- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section;
// 设置分区footerView的视图
- (nullable UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section;
// 点击触发事件
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;

刷新UITableView

试想一下,当我们创建UITableView完成之后,我们向其中添加或者更改删除了一条数据,我们想让最新的数据呈现出来该怎么办呢?很自然的我们就想到了重新加载一遍,也就是刷新了.

关于刷新数据系统提供给我们三种方法.

// 刷新整个表格
-(void)reloadData;
// 刷新某些section,animation表示刷新时使用的动画
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
// 刷新某些row
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;

UITableViewController

上面说了这么多,我们发现创建一个表视图真的好麻烦,要设置代理和数据源,还有那么多的方法.那么有没有一种更好的方法来替代这些方法呢?答案当然是肯定的.为了提高开发效率,Apple将UITableView和UIViewController结合,产生了UITableViewController.

使用UITableViewController,我们只需要根据需要使用其方法进行配置就好了.如果搞懂了UITableView,那么使用UITableViewController就得心应手了,其实这两个如出一辙,在此就不赘述了.

总结

以上就是UITableView和UITableViewCell的简单介绍,如果有疑问或者错误,欢迎指正交流,我将不胜感激.转载请保留链接.