TableView的一些事

UITableView的基本信息

UITableView的基本信息

初始化方法

- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style;
这个方法初始化表视图的frame大小并且设置一个风格,UITableViewStyle是一个枚举,如下:

1
2
3
4
typedef NS_ENUM(NSInteger, UITableViewStyle) {    
UITableViewStylePlain, // 标准的表视图风格
UITableViewStyleGrouped // 分组的表视图风格
};

常用属性

获取表视图的风格(只读属性)
@property(nonatomic, readonly) UITableViewStyle style;

设置表示图代理和数据源代理(代理方法后面讨论)
@property (nonatomic, assign)id <UITableViewDataSource> dataSource;
@property(nonatomic, assign)id <UITableViewDelegate> delegate;

设置表示图的行高(默认为44)
@property(nonatomic)CGFloat rowHeight;

设置分区的头视图高度和尾视图高度(当代理方法没有实现时才有效)
@property(nonatomic)CGFloat sectionHeaderHeight;
@property (nonatomic)CGFloat sectionFooterHeight;

设置一个行高的估计值(默认为0,表示没有估计,7.0之后可用)
@property (nonatomic)CGFloat estimatedRowHeight;
注意:这个属性官方的解释是如果你的tableView的行高是可变的,那么设计一个估计高度可以加快代码的运行效率。

下面这两个属性和上面相似,分别设置分区头视图和尾视图的估计高度(7.0之后可用)
@property (nonatomic)CGFloat estimatedSectionHeaderHeight;
@property (nonatomic)CGFloat estimatedSectionFooterHeight;

设置分割线的位置
@property (nonatomic)UIEdgeInsets separatorInset;
如果细心,你可能会发现系统默认的tableView的分割线左端并没有顶到边沿。通过这个属性,可以手动设置分割线的位置偏移,比如你向让tableView的分割线只显示右半边,可以如下设置:

1
2
UITableView *tab=[[UITableView alloc]initWithFrame:self.view.frame style:UITableViewStylePlain];
tab.separatorInset=UIEdgeInsetsMake(0, tab.frame.size.width/2, 0,0);

设置tableView背景view视图
@property(nonatomic, readwrite, retain) UIView *backgroundView;

常用方法详解

重载tableView
- (void)reloadData;

重载索引栏
- (void)reloadSectionIndexTitles;
这个方法常用语新加或者删除了索引类别而无需刷新整个表视图的情况下。

获取分区数
- (NSInteger)numberOfSections;

根据分区获取行数
- (NSInteger)numberOfRowsInSection:(NSInteger)section;

获取分区的大小(包括头视图,所有行和尾视图)
- (CGRect)rectForSection:(NSInteger)section;

根据分区分别获取头视图,尾视图和行的高度
- (CGRect)rectForHeaderInSection:(NSInteger)section;
- (CGRect)rectForFooterInSection:(NSInteger)section;
- (CGRect)rectForRowAtIndexPath:(NSIndexPath *)indexPath;

获取某个点在tableView中的位置信息
- (NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point;

获取某个cell在tableView中的位置信息
- (NSIndexPath *)indexPathForCell:(UITableViewCell *)cell;

根据一个矩形范围返回一个信息数组,数组中是每一行row的位置信息
- (NSArray *)indexPathsForRowsInRect:(CGRect)rect;

通过位置路径获取cell
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath;

获取所有可见的cell
- (NSArray *)visibleCells;

获取所有可见行的位置信息
- (NSArray *)indexPathsForVisibleRows;

根据分区获取头视图
- (UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section;

根据分区获取尾视图
- (UITableViewHeaderFooterView *)footerViewForSection:(NSInteger)section;

使表示图定位到某一位置(行)
- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated;
注意:indexPah参数是定位的位置,决定于分区和行号。animated参数决定是否有动画。scrollPosition参数决定定位的相对位置,它使一个枚举,如下:

1
2
3
4
5
6
typedef NS_ENUM(NSInteger, UITableViewScrollPosition) {   
UITableViewScrollPositionNone,//同UITableViewScrollPositionTop
UITableViewScrollPositionTop,//定位完成后,将定位的行显示在tableView的顶部
UITableViewScrollPositionMiddle,//定位完成后,将定位的行显示在tableView的中间
UITableViewScrollPositionBottom//定位完成后,将定位的行显示在tableView最下面
};

使表示图定位到选中行
- (void)scrollToNearestSelectedRowAtScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated;
这个函数与上面的非常相似,只是它是将表示图定位到选中的行。

tableView操作刷新块的应用

在介绍动画块之前,我们先看几个函数:
插入分区
- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
animation参数是一个枚举,枚举的动画类型如下

1
2
3
4
5
6
7
8
9
typedef NS_ENUM(NSInteger, UITableViewRowAnimation) {    
UITableViewRowAnimationFade,//淡入淡出
UITableViewRowAnimationRight,//从右滑入
UITableViewRowAnimationLeft,//从左滑入
UITableViewRowAnimationTop,//从上滑入
UITableViewRowAnimationBottom,//从下滑入
UITableViewRowAnimationNone, //没有动画
UITableViewRowAnimationMiddle,
UITableViewRowAnimationAutomatic = 100 // 自动选择合适的动画};

删除分区
- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
重载一个分区
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation ;
移动一个分区
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection;
插入一些行
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
删除一些行
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
重载一些行
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
移动某行
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath;
了解了上面几个函数,我们来看什么是操作刷新块:
当我们调用的上面的函数时,tableView会立刻调用代理方法进行刷新,如果其中我们所做的操作是删除某行,而然数据源数组我们可能并没有刷新,程序就会崩溃掉,原因是代理返回的信息和我们删除后不符。
IOS为我们提供了下面两个函数解决这个问题:
开始块标志
- (void)beginUpdates;
结束快标志
- (void)endUpdates;
我们可以将我们要做的操作全部写在这个块中,那么,只有当程序执行到结束快标志后,才会调用代理刷新方法。代码示例如下:

1
2
3
4
[tab beginUpdates];  
[tab deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:1 inSection:0]] withRowAnimation:UITableViewRowAnimationLeft];
[dataArray removeObjectAtIndex:1];
[tab endUpdates];

注意:不要在这个块中调用reloadData这个方法,它会使动画失效。

tableView的编辑操作

设置是否是编辑状态(编辑状态下的cell左边会出现一个减号,点击右边会划出删除按钮)
@property (nonatomic, getter=isEditing) BOOL editing;
- (void)setEditing:(BOOL)editing animated:(BOOL)animated;

设置cell是否可以被选中(默认为YES)
@property (nonatomic) BOOL allowsSelection;

设置cell编辑模式下是否可以被选中
@property (nonatomic) BOOL allowsSelectionDuringEditing;

设置是否支持多选
@property (nonatomic) BOOL allowsMultipleSelection;

设置编辑模式下是否支持多选
@property (nonatomic) BOOL allowsMultipleSelectionDuringEditing;

选中cell的相关操作

获取选中cell的位置信息
- (NSIndexPath *)indexPathForSelectedRow;

获取多选cell的位置信息
- (NSArray *)indexPathsForSelectedRows;

代码手动选中与取消选中某行
- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition;
- (void)deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated;
注意:这两个方法将不会回调代理中的方法。

tableView附件的相关方法

设置索引栏最小显示行数
@property (nonatomic) NSInteger sectionIndexMinimumDisplayRowCount;

设置索引栏字体颜色
@property (nonatomic, retain) UIColor *sectionIndexColor;

设置索引栏背景颜色
@property (nonatomic, retain) UIColor *sectionIndexBackgroundColor;

设置索引栏被选中时的颜色
@property (nonatomic, retain) UIColor *sectionIndexTrackingBackgroundColor;

设置分割线的风格
@property (nonatomic) UITableViewCellSeparatorStyle separatorStyle;
这个风格是一个枚举,如下:

1
2
3
4
5
typedef NS_ENUM(NSInteger, UITableViewCellSeparatorStyle) {    
UITableViewCellSeparatorStyleNone,//无线
UITableViewCellSeparatorStyleSingleLine,//有线
UITableViewCellSeparatorStyleSingleLineEtched
};

设置分割线颜色
@property (nonatomic, retain) UIColor *separatorColor;

设置分割线毛玻璃效果(IOS8之后可用)
@property (nonatomic, copy) UIVisualEffect *separatorEffect;
注意:这个属性是IOS8之后新的。

设置tableView头视图
@property (nonatomic, retain) UIView *tableHeaderView;

设置tableView尾视图
@property (nonatomic, retain) UIView *tableFooterView;

从复用池中取cell
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;

获取一个已注册的cell
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath

从复用池获取头视图或尾视图
- (id)dequeueReusableHeaderFooterViewWithIdentifier:(NSString *)identifier;

通过xib文件注册cell
- (void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier;

通过OC类注册cell
- (void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier
上面两个方法是IOS6之后的方法。

通过xib文件和OC类获取注册头视图和尾视图
- (void)registerNib:(UINib *)nib forHeaderFooterViewReuseIdentifier:(NSString *)identifier;
- (void)registerClass:(Class)aClass forHeaderFooterViewReuseIdentifier:(NSString *)

iOS UITableView的代理方法

UITableViewDataSource(数据源代理)

1、必须实现的回调方法
返回每个分区的行数
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

返回每一行的cell
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

2、可选实现的方法
返回分区数(默认为1)
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;

返回每个分区头部的标题
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;

返回每个分区的尾部标题
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section;

设置某行是否可编辑
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath;

设置某行是否可以被移动
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath;

设置索引栏标题数组(实现这个方法,会在tableView右边显示每个分区的索引)
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView;

设置索引栏标题对应的分区
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index

tableView接受编辑时调用的方法
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath;
这个方法中的editingStyle参数是一个枚举,代表了cell被编辑的模式,如下:

1
2
3
4
5
typedef NS_ENUM(NSInteger, UITableViewCellEditingStyle) {  
UITableViewCellEditingStyleNone,//没有编辑操作
UITableViewCellEditingStyleDelete,//删除操作
UITableViewCellEditingStyleInsert//插入操作
};

tableView的cell被移动时调用的方法
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;

UITableViewDelegate(tableView代理)

cell将要显示时调用的方法
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;

头视图将要显示时调用的方法
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section;

尾视图将要显示时调用的方法
- (void)tableView:(UITableView *)tableView willDisplayFooterView:(UIView *)view forSection:(NSInteger)section;

和上面的方法对应,这三个方法分别是cell,头视图,尾视图已经显示时调用的方法
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath;
- (void)tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:(NSInteger)section;
- (void)tableView:(UITableView *)tableView didEndDisplayingFooterView:(UIView *)view forSection:(NSInteger)section;

设置行高,头视图高度和尾视图高度的方法
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;

设置行高,头视图高度和尾视图高度的估计值(对于高度可变的情况下,提高效率)
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath*)indexPath;
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForFooterInSection:(NSInteger)section;

设置自定义头视图和尾视图
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section;
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section;

设置cell是否可以高亮
- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath;

cell高亮和取消高亮时分别调用的函数
- (void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath;

当即将选中某行和取消选中某行时调用的函数,返回一直位置,执行选中或者取消选中
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath*)indexPath;
- (NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(NSIndexPath*)indexPath;

已经选中和已经取消选中后调用的函数
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath;

设置tableView被编辑时的状态风格,如果不设置,默认都是删除风格
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath;

自定义删除按钮的标题
- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath;

下面这个方法是IOS8中的新方法,用于自定义创建tableView被编辑时右边的按钮,按钮类型为UITableViewRowAction。
- (NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath*)indexPath ;

设置编辑时背景是否缩进
- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath*)indexPath;

将要编辑和结束编辑时调用的方法
- (void)tableView:(UITableView*)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView*)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath;

移动特定的某行
- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath;

TableView性能优化

tableview优化两个思路:缓存操作和异步操作。
最主要:复用cell,header,footer实例;使用约束布局cell子控件时不多次添加约束;图片不过大,尽量不使用透明视图;避免阻塞主线程;计算高度方法不做大量逻辑处理。

cell复用机制。

如果每次都创建新的cell,在滑动的时候会表现为:刚开始的时候很顺畅,但是会越来越卡,内存跟着一直升高,停止滑动的时候也不会降下来。使用缓存机制创建的cell,开始滑动的时候内存会开始上升,等创建了一个屏幕再加半屏的cell之后,内存趋于平稳。

  1. 先注册cell

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @interface HomeVC ()<UITableViewDataSource, UITableViewDelegate>

    @property (nonatomic, strong) UITableView *tableView;

    @end

    static NSString *cellId = @"Cell";

    @implementation HomeVC

    - (void)viewDidLoad {
    [super viewDidLoad];

    self.myTableView.delegate = self;
    self.myTableView.dataSource = self;
    //第一种注册cell<nib文件类HomeTableViewCell>
    [self.tableView registerNib:[UINib nibWithNibName:@"HomeTableViewCell" bundle:nil] forCellReuseIdentifier:cellId];

    //第二种注册Cell<纯手工打造的HomeVC>
    // [self.tableView registerClass:[HomeVC class]forCellReuseIdentifier:cellId];
    }
  2. 重用相关API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 复用 Cell:
- [UITableView dequeueReusableCellWithIdentifier:];
- [UITableView registerNib:forCellReuseIdentifier:];
- [UITableView registerClass:forCellReuseIdentifier:];
- [UITableView dequeueReusableCellWithIdentifier:forIndexPath:];
// 复用 Section 的 Header/Footer:
- [UITableView registerNib:forHeaderFooterViewReuseIdentifier:];
- [UITableView registerClass:forHeaderFooterViewReuseIdentifier:];
- [UITableView dequeueReusableHeaderFooterViewWithIdentifier:];
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
//获取重用池中的cell
HomeTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
//如果没有取到,就初始化
if (!cell) {
cell = [[HomeTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
}
cell.cellNameLab.text = @"Name";
return cell;
}

cell使用drawRect方法添加子控件

如果添加了大量的子控件,使用drawRect方法添加子控件,平衡GPU与CPU的负担。同时还需要注意尽量使用不透明视图和不重叠的渐变,否则会加大GPU的负担,造成性能不佳。

高度计算方法时不做复杂的计算,尽量只使用加减乘除。

使用 - (CGSize)systemLayoutSizeFittingSize: (CGSize)targetSize
方法计算。

避免快速滑动情况下开过多线程。

cell中的图片开线程异步加载<SDWebImage(异步操作)>。但是线程开过多了会造成资源浪费,内存开销过大。图片过多时可以不要一滚动就走cellForRow方法,可以在scrollview的代理方法中做限制,当滚动开始减速的时候才加载显示在当前屏幕上的cell(通过tableview的dragging和declearating两个状态也能判断)

1
2
3
4
5
6
7
8
9
10
11
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath{
BOOL canLoad:Bool = !tableView.dragging && !tableView.declearating
if canLoad {
//开始loaddata,异步加载图片
}
}
-(void)scrollViewDidScroll:(UIScrollView *)sender{
//刷新tableview
//在此刷新的是屏幕上显示的cell的内容
[self.table reloadData];
}

图片处理

1)后台下载图片后再回主线程刷新UI,避免阻塞主线程。
2)图片过大回造成GPU负担过大,可以在图片下载后压缩尺寸后显示
3)避免对layer做过多的操作,尽量设置图片为不透明

tableView的数据刷新

对tableView的刷新直接就是reloadData.调用这个方法之后,控制器会把数据源方法重新执行一遍,如果前面我们的数据需要保存,reloadData重新执行一遍,我们的数据都会被刷新掉,那么我改如何解决这个问题呢 ? 这就我们对其中某一个分组,或者某一个指定的Cell进行刷新.具体的代码实现如下,

1
2
3
4
5
6
7
//一个section刷新    
NSIndexSet *indexSet=[[NSIndexSet alloc]initWithIndex:2];
[tableview reloadSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];
//一个指定cell刷新
NSIndexPath *indexPath=[NSIndexPath indexPathForRow:3 inSection:0];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath,nil]
withRowAnimation:UITableViewRowAnimationNone];

更多参考资料:

优化UITableViewCell高度计算的那些事:http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/
objc的期刊:更轻量的View Controller:https://objccn.io/issue-1-1/

文章目录
  1. 1. UITableView的基本信息
    1. 1.1. 初始化方法
    2. 1.2. 常用属性
    3. 1.3. 常用方法详解
  2. 2. tableView操作刷新块的应用
  3. 3. tableView的编辑操作
  4. 4. 选中cell的相关操作
  5. 5. tableView附件的相关方法
  6. 6. iOS UITableView的代理方法
    1. 6.1. UITableViewDataSource(数据源代理)
    2. 6.2. UITableViewDelegate(tableView代理)
  7. 7. TableView性能优化
    1. 7.1. cell复用机制。
    2. 7.2. cell使用drawRect方法添加子控件
    3. 7.3. 高度计算方法时不做复杂的计算,尽量只使用加减乘除。
    4. 7.4. 避免快速滑动情况下开过多线程。
    5. 7.5. 图片处理
  8. 8. tableView的数据刷新
    1. 8.1. 更多参考资料:
本站总访问量 本站访客数人次 ,