记录寄己走过的路

iOS UI控件详解—「UITableView表格视图」

引导


“ 本文不适合老司机… ”

本文章将介绍 iOS UI布局 详解,最常使用的三大控件之一 UITableView,将会分成两篇文章完整的讲述UITableView的常用属性方法使用(包括优化方面)及注意点 和 实战开发使用场景案例,文章编写周期会长一些,用到那点实用的东西,会及时补充。

本篇文章主要从【UITableView 属性方法注解】学习总结。
在「时间 & 知识 」有限内,总结的文章难免有「未全、不足 」的地方,还望各位好友指出,以提高文章质量。

目录:

  1. 概念
    1.UITableView的概念
    2.UITableViewCell的概念
    3.UITableViewController的概念
  2. UITableView的使用步骤
    1.大致个人总结六步
  3. UITableView的常用属性
  4. UITableViewCell的常用属性
  5. UITableView的代理方法
  6. UITableViewController的使用
  7. UITableViewCell的重用
  8. UITableViewCell的优化
  9. UITableView重要属性图解
  10. UITableView.h 属性&方法
  11. 期待后续 & About me

概念


UITableView的概念



  • 简化可释义为 "表格视图" 或是 "列表视图", 且 支持选择和编辑的信息。在iOS开发中做列表数据类型的应用,最常用的做法就是使用UITableView控件。

  • UITbaleView 继承自 UIScrollView,因此支持竖直滚动,而且在 UIScrollView 做了性能优化,当然你也可以做到水平滚动,把cell 旋转一下就可以了。

UITableViewCell的概念



  • 简化意思就是 UITableView的每一行都是一个UITableViewCell。其中类包含属性和方法用于设置和管理单元格;

  • UITableView内部有个默认的子控件:contentView,填充整个UITableViewCell的父控件。

  • UITableView的子控件实质都在contentView上。

UITableViewController的概念



  • 简化意思就是 这个类是一个自带(管理)tableView 的控制器。

  • Xcodecmd UITableViewController 点进去,看到

    1
    NS_CLASS_AVAILABLE_IOS(2_0) @interface UITableViewController : UIViewController

简化意思就是UITableViewController是已经遵循了UITableViewDelegateUITableViewDataSource代理的控制器

UITableView使用步骤


下面示例以 tableView代码自定义不等高Cell 为例:

  • 第一步:创建 UITableView,采用懒加载方式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (UITableView *)tableView {
if (_tableView == nil) {
_tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0,kScreenWidth, kScreenHeight) style:UITableViewStylePlain];// Plain单组数据悬停 Grouped多组数据不悬停
_tableView.dataSource = self;// 数据源
_tableView.delegate = self;// 代理
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;// 分割线
_tableView.estimatedRowHeight = 100;// 预设高度
//[self.view addSubview:self.tableView];// 这里不要忘记添加上
//--------------------------- tableView 常用属性 ------------------------------//
//
}
return _tableView;
}
  • 第二步:设置数据源和代理,遵守对应协议。

    1
    @interface LNTableViewAttributeVC ()
  • 第三步:设置数据数组,采用懒加载方式加载数据(使用MJExtension字典数组转模型数组)。

1
2
3
4
5
6
7
- (NSMutableArray *)dataArray{
if (!_dataArray) {
_dataArray = [LNStatus mj_objectArrayWithFilename:@"statuses.plist"];
}
return _dataArray;
}
  • 第四步:创建自定义Cell(视图继承UITableViewCell)。
    • 1.定义模型类对象。
    • 2.定义子控件属性。
    • 3.在initWithStyle:style reuseIdentifier:reuseIdentifier,添加子控件(设置约束Masonry)。
    • 4.layoutSubviews 设置子控件的 frame.
    • 5.模型类对象set方法setStatus:设置子控件的数据(赋值)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// LNStatusViewCell.h
@class LNStatus;
@interface LNStatusViewCell : UITableViewCell
/** 模型类对象 */
@property (nonatomic, strong) LNStatus *status;
@end
//--------------------------- <#我是分割线#> ------------------------------//
// LNStatusViewCell.m
@interface LNStatusViewCell ()
/* 头像 */
@property (nonatomic, weak) UIImageView *iconImageView;
/* 配图 */
@property (nonatomic, weak) UIImageView *pictureImageView;
/* vip */
@property (nonatomic, weak) UIImageView *vipImageView;
/* 名称 */
@property (nonatomic, weak) UILabel *nameLabel;
/** 文字 */
@property (nonatomic, weak) UILabel *text_Label;// 自定义控件不要与系统textLabel重名
@end
@implementation LNStatusViewCell
/*1.
添加子控件(设置约束)(如:文字和颜色,一次性的设置)
注意点:把所有有可能显示的子控件都先添加进去
*/
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
// 图像
// 配图
// vip
// 昵称
// 正文
// 自定义分割线
UILabel *lineLabel = [[UILabel alloc] initWithFrame:CGRectZero];
lineLabel.backgroundColor = [UIColor colorWithWhite:0.5 alpha:0.5];
[self.contentView addSubview:lineLabel];
[lineLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.left.right.equalTo(self.contentView).with.offset(0);
make.height.mas_equalTo(1.0f);
}];
}
return self;
}
// 2.设置子控件的frame
/*
- (void)layoutSubviews{
[super layoutSubviews];
}
*/
// 3.set方法设置子控件的数据(赋值)
- (void)setStatus:(LNStatus *)status{
_status = status;
self.iconImageView.image = [UIImage imageNamed:status.icon];
self.nameLabel.text = status.name;
self.text_Label.text = status.text;
if (status.isVip) {// 有无皇冠
self.vipImageView.hidden = NO;
self.nameLabel.textColor = [UIColor redColor];
} else {
self.vipImageView.hidden = YES;
self.nameLabel.textColor = [UIColor blackColor];
}
if (status.picture) {// 有无配图(有配图再赋值,无配图就不赋值)
self.pictureImageView.hidden = NO;
self.pictureImageView.image = [UIImage imageNamed:status.picture];
} else {
self.pictureImageView.hidden = YES;
}
/** 这里的下文会介绍到 */
self.iconImageView.frame = self.status.iconFrame;
self.nameLabel.frame = self.status.nameFrame;
self.vipImageView.frame = self.status.vipFrame;
self.text_Label.frame = self.status.textFrame;
self.pictureImageView.frame = self.status.pictureFrame;
}
  • 第五步:创建自定义模型类(模型继承NSObject)。
    • 1.定义模型属性。
    • 2.提供构造方法(对象方法和类方法)。
    • 3.定义对应模型frame属性(注解:Cell的动态行高,这里解决方案: 在heightForRowAtIndexPath:这个方法返回之前就要计算cell的高度,即把cell子控件的frame封装到模型中,cellHeight返回行高(相当于懒加载))。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@interface LNStatus : NSObject
// 1.定义模型属性
/* 文字 */
@property (nonatomic, copy) NSString *text;
/* 头像 */
@property (nonatomic, copy) NSString *icon;
/* 名称 */
@property (nonatomic, copy) NSString *name;
/** VIP 到时候访问的时候可以.vip ,也可以.isVip */
@property (nonatomic, assign, getter=isVip) BOOL vip;
/* 配图 */
@property (nonatomic, copy) NSString *picture;
/** 用MJExtension字典转模型框架,下面可以不写 */
// 2.提供构造方法
//+ (instancetype)statusWithDict:(NSDictionary *)dict;
//--------------------------- 返回cell的高度封装 ------------------------------//
//
/** 图像的frame */
@property (nonatomic, assign) CGRect iconFrame;
/** 昵称的frame */
@property (nonatomic, assign) CGRect nameFrame;
/** vip的frame */
@property (nonatomic, assign) CGRect vipFrame;
/** 正文frame */
@property (nonatomic, assign) CGRect textFrame;
/** 配图的frame */
@property (nonatomic, assign) CGRect pictureFrame;
/** 返回cell的高度 */
@property (nonatomic, assign) CGFloat cellHeight;
// LNStatus.m
//+ (instancetype)statusWithDict:(NSDictionary *)dict {
// // 字典赋值模型属性
//}
// get方法返回行高(相当于懒加载)
- (CGFloat)cellHeight{
if (_cellHeight == 0) {
/** 图像 */
self.iconFrame = CGRectMake(iconX, iconY, iconWH, iconWH);
/** 昵称:字符串自适应宽(昵称文字没有换行) */
self.nameFrame = CGRectMake(nameX, nameY, nameSize.width, nameSize.height);
/** vip */
if (self.isVip) {
self.vipFrame = CGRectMake(vipX, vipY, vipW, vipH);
}
/** 正文:字符串自适应宽高(文字有换行) */
self.textFrame = CGRectMake(textX, textY, textW, textH);
/** 配图 */
if (self.picture) { // 有配图
self.pictureFrame = CGRectMake(pictureX, pictureY, pictureWH, pictureWH);
_cellHeight = CGRectGetMaxY(self.pictureFrame) + space;
} else {
_cellHeight = CGRectGetMaxY(self.textFrame) + space;
}
}
return _cellHeight;
}
  • 第六步:实现DataSource数据源(必须)Delegate代理(可选)协议方法创建自定义模型类(模型继承NSObject)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#pragma mark ------------------
#pragma mark - UITableViewDataSource
// TableView中 有多少组Sections
// 说明:单组数据可不实现方法,默认返回1
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
// 每组Sections 有多少行Rows
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.dataArray.count;
}
// 每行 cell内容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
// 1.cell注册(只要用到forIndexPath:就必须要注册)
//[self.tableView registerClass:[LNStatusViewCell class] forCellReuseIdentifier:cellID];
// 2.cell复用队列(访问缓存池)
LNStatusViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID forIndexPath:indexPath];
// 3.设置数据(数据数组赋值模型类)
cell.status = self.dataArray[indexPath.row];
return cell;
}

1
2
3
4
5
6
7
8
9
#pragma mark ------------------
#pragma mark - UITableViewDelegate
// 解决方案:在这个方法返回之前就要计算cell的高度,即返回cell的高度封装在模型中
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
LNStatus *status = self.dataArray[indexPath.row];
return status.cellHeight;
}

UITableView基本使用效果图


UITableView基础.gif

UITableView常用属性


  • 全局设置行row高:
1
2
3
4
5
6
7
8
属性:
@property (nonatomic) CGFloat rowHeight;
使用格式:
self.tableView.rowHeight = 70;
说明:
默认是44
  • 全局设置区头(区尾)高:
1
2
3
4
5
6
7
属性:
@property (nonatomic) CGFloat sectionHeaderHeight;
@property (nonatomic) CGFloat sectionFooterHeight;
使用格式:
self.tableView.sectionHeaderHeight = 50;
self.tableView.sectionFooterHeight = 50;
  • 设置分割线的样式、铺满和颜色:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
属性:
@property (nonatomic) UITableViewCellSeparatorStyle separatorStyle __TVOS_PROHIBITED;
@property (nonatomic, strong, nullable) UIColor *separatorColor UI_APPEARANCE_SELECTOR __TVOS_PROHIBITED;
@property (nonatomic) UIEdgeInsets separatorInset NS_AVAILABLE_IOS(7_0) UI_APPEARANCE_SELECTOR;
使用格式:
self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
self.tableView.separatorColor = [UIColor redColor];
self.tableView.separatorInset = UIEdgeInsetsMake(0, 0, 0, 0);
说明:
设置 StyleNone 代表隐藏分割线,[UIColor clearColor]为透明色对象
枚举UITableViewCellSeparatorStyle常用的枚举元素:
UITableViewCellSeparatorStyleNone // 隐藏分割线
UITableViewCellSeparatorStyleSingleLine // 默认样式
UITableViewCellSeparatorStyleSingleLineEtched // 仅支持在grouped样式,但是和默认样式没什么区别
  • 设置tableView表格头(尾)视图:
1
2
3
4
5
6
7
属性:
@property (nonatomic, strong, nullable) UIView *tableHeaderView;
@property (nonatomic, strong, nullable) UIView *tableFooterView;
使用格式:
self.tableView.tableHeaderView = [[UISwitch alloc] init];
self.tableView.tableFooterView = [[UISwitch alloc] init];
  • 设置table背景视图和颜色:
1
2
3
4
5
6
7
属性:
@property (nonatomic, strong, nullable) UIView *backgroundView NS_AVAILABLE_IOS(3_2);
@property (nonatomic, copy, nullable) UIColor *backgroundColor;
使用格式:
self.tableView.backgroundView = [[UIView alloc] init];
self.tableView.backgroundColor = [UIColor grayColor];
  • 设置TableView的cell的预设高度(性能优化):
1
2
3
4
5
6
7
8
属性:
@property (nonatomic) CGFloat estimatedRowHeight NS_AVAILABLE_IOS(7_0);
使用格式:
self.tableView.estimatedRowHeight = 100;
说明:
这里的高度也不是越大越好,要适时而定
  • 隐藏多余分割线:
1
2
使用格式:
self.tableView.tableFooterView = [[UIView alloc] init];
  • 设置右侧索引文字和背景颜色:
1
2
3
4
5
6
7
属性:
@property (nonatomic, strong, nullable) UIColor *sectionIndexColor NS_AVAILABLE_IOS(6_0) UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong, nullable) UIColor *sectionIndexBackgroundColor NS_AVAILABLE_IOS(7_0) UI_APPEARANCE_SELECTOR;
使用格式:
self.tableView.sectionIndexColor = [UIColor grayColor];
self.tableView.sectionIndexBackgroundColor = [UIColor yellowColor];

UITableViewCell的常用属性


  • 设置cell附加样式(比如右侧的箭头):
1
2
3
4
5
属性:
@property (nonatomic) UITableViewCellAccessoryType accessoryType;
使用格式:
cell.accessoryType = UITableViewCellAccessoryCheckmark;
  • 设置cell右边的指示控件(比如右侧的开关):
1
2
3
4
5
属性:
@property (nonatomic, strong) UIView *accessoryView;
使用格式:
cell.accessoryView = [[UISwitch alloc] init];
  • 设置cell选中样式(StyleNone选中不变灰色):
1
2
3
4
5
属性:
@property (nonatomic) UITableViewCellSelectionStyle selectionStyle;
使用格式:
cell.selectionStyle = UITableViewCellSelectionStyleNone;
  • 设置cell的背景控件:
1
2
3
4
5
6
7
8
9
10
属性:
@property (nonatomic, strong) UIView *backgroundView;
使用格式:
UIView *bg = [[UIView alloc] init];
bg.backgroundColor = [UIColor redColor];
cell.backgroundView = bg;
说明:
背景控件 和 背景颜色 同时存在时,(优先级: backgroundView > backgroundColor)
  • 设置cell的背景颜色:
1
2
3
4
5
6
7
8
9
属性:
@property(nullable, nonatomic,copy) UIColor *backgroundColor
使用格式:
cell.backgroundColor = [UIColor blueColor];
说明:
还可以设置cell的子控件背景图片
cell.textLabel.backgroundColor = [UIColor greenColor];
  • 设置cell选中时背景View:
1
2
3
4
5
6
7
属性:
@property (nonatomic, strong) UIView *selectedBackgroundView;
使用格式:
UIView *seletedBg = [[UIView alloc] init];
seletedBg.backgroundColor = [UIColor purpleColor];
cell.selectedBackgroundView = seletedBg;

UITableView的代理方法


  • 返回每一行cell的高度:
1
2
3
4
5
6
7
8
方法 和 使用格式:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
// 获取indexPath
// if (indexPath.row%2) {
// return 100;
// }else
return 44;
}
  • 当选中某一行cell的时候就会调用这个方法:
1
2
3
4
方法 和 使用格式:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(@"选中第%ld区---第%ld行 ",indexPath.section,indexPath.row);
}
  • 返回每一组的区头(区尾) 标题 和 控件:
1
2
3
4
5
6
7
8
9
10
11
12
13
方法 和 使用格式:
// 区头(区尾)标题
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
return @"简书-ln";
}
// 区头(区尾)View
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
return [[UISwitch alloc] init];
}
说明:
标题 和 View 同时存在时,View 会覆盖 Title
  • 返回每一组的头部(尾部)高度:
1
2
3
4
5
6
7
8
9
方法 和 使用格式:
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
// 获取indexPath
NSIndexPath *indexPath = [[NSIndexPath alloc]initWithIndex:section];
if (indexPath.section == 0) {
return 60;
}else
return 30;
}
  • 返回右侧索引标题:
1
2
3
4
5
方法 和 使用格式:
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView{
//return [self.dataArray valueForKeyPath:@"title"];
return @[@"黄焖鸡小份",@"黄焖鸡中份",@"黄焖鸡大份",];
}
  • 返回某一行缩进:
1
2
3
4
方法 和 使用格式:
- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath{
return 7;
}

UITableViewController的使用


  • 将控制器设置为UITableView的方法和步骤

    • 第一步:创建新的类或修改原有的ViewController类,继承自UITableViewController

    • 第二步:在Main.storyboard中删除自带的UIViewController控制器,然后往里面拖一个UITableViewController控制器

    • 第三步:修改新拖进来的TableViewController控制器的自定义类名为第一步中继承自UITableViewController类的类名

    • 第四步:勾选TableViewController控制器为程序启动第一个加载的控制器

  • 注意点:

    • tableVieController有个tableView属性,指向一个tableView
    • tableViewdataSourcedelegate属性指向的就是这个控制器,并且这个控制器已经遵守了UITableViewDataSourceUITableViewDelegate
    • 每个控制器的内部都有一个view属性,在tableVieController中,viewtableView属性指向的是同一个对象(控制器的view就是tableView)。

UITableViewCell的重用


  • 原因:
    • iOS设备的内存有限,如果用 UITableView显示成千上万条数据,就需要成千上万个UITableViewCell对象的话,那将会耗尽iOS设备的内存。
    • 有可能导致显示数据错乱。
  • 原理:
    • 当滚动列表时,部分UITableViewCell会移出窗口,UITableView会将窗口外的UITableViewCell放入一个缓存池中,等待重用
    • UITableView要求dataSource返回UITableViewCell时,dataSource会先查看这个对象池,如果池中有未使用的UITableViewCelldataSource会用新的数据配置这个UITableViewCell,然后返回给UITableView,重新显示到窗口中,从而避免创建新的UITableViewCell对象
  • Cell重用的实现代码
  • 方法一:
    • 1.定义一个cell的重用标识
    • 2.根据这个ID去缓存池中看有没有可循环利用的cell
    • 3.如果缓存池中没有可循环利用的cell,自己创建
1
2
3
4
5
6
7
8
9
10
11
12
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1. 定义一个重用标识
static NSString *cellID = @"cellID";
// 2. 根据这个cellID去缓存池中看有没有可循环利用的cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cellID"];
// 3. 如果缓存池中没有可循环利用的cell, 自己创建
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cellID"];
}
return cell;
}
  • 方法二:
    • 1.定义一个cell的重用标识。
    • 2.根据这个ID去缓存池中看有没有可循环利用的cell
    • 3.如果缓存池中没有会看有没有,根据ID这个标识注册对应的cell类型。
    • 4.如果有注册,会根据这个ID创建对应的类型的cell,并且会绑定这个ID标识,返回这个cell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1. 定义一个重用标识
static NSString *cellID = @"CellID";
static NSString *testID = @"testID";
- (void)viewDidLoad {
[super viewDidLoad];
// 3. 根据ID 这个标识 注册 对应的cell类型是UITableViewCell
[self.tableView registerClass:[LNTableViewCell class] forCellReuseIdentifier:cellID];
[self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([LNTableViewXibCell class]) bundle:nil] forCellReuseIdentifier: testID];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// 2. 根据这个ID去缓存池中看有没有可循环利用的cell
LNTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (indexPath.row%2 == 0) {
LNTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CellID];
return cell;
}else{
LNTableViewXibCell *cell = [tableView dequeueReusableCellWithIdentifier:testID];
return cell;
}
}
  • 总结:
    • 不同类型的Cell共存(重要点是CellID不同)。

UITableViewCell的优化


  • UITableView 的优化主要从三个方面入手:

    • 1.提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法;

    • 2.异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;

    • 3.滑动时按需加载,按照用户滚动的速度去选择加载哪个cell。这个在大量图片展示,网络加载的时候很管用!(SDWebImage已经实现异步加载,配合这条性能杠杠的)。

  • 除了上面最主要的三个方面外,还有很多几乎大伙都很熟知的优化点:

    • 1.正确使用reuseIdentifier来重用Cells
    • 2.尽量使所有的view opaque,包括Cell自身。
    • 3.尽量少用或不用透明图层。
    • 4.如果Cell内显示的内容来自web,使用异步加载,缓存请求结果。
    • 5.减少subviews的数量。
    • 6.在heightForRowAtIndexPath:中尽量不使用。cellForRowAtIndexPath:,如果你需要用到它,只用一次然后缓存结果。
    • 7.尽量少用addViewCell动态添加View,可以初始化时就添加,然后通过hide来控制是否显示。
  • 案例(按需加载):
    • 原理:在快速滑动松手后滚动的cell个数超过预定的个数,只显示最后出现的cell的前三个cell,把这三个cellindexPath存到数组中,在数据源方法里判断如果数组count>0,且数组不包含当前的indexPath,那就说明此cell是在快速滑动中需要隐藏的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//按需加载 - 如果目标行与当前行相差超过指定行数,只在目标滚动范围的前后指定3行加载。
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
NSIndexPath *ip = [_titleTableView indexPathForRowAtPoint:CGPointMake(0, targetContentOffset->y)];
NSIndexPath *cip = [[_titleTableView indexPathsForVisibleRows] firstObject];
NSInteger skipCount = 1; // 这里我为了方便演示写的1,大家可以按需求自行设定
if (labs(cip.row-ip.row)>skipCount) {
// 此方法可以获取将要显示的组
// visibleSections = [NSSet setWithArray:[[_titleTableView indexPathsForVisibleRows] valueForKey:@"section"]];
NSArray *temp = [_titleTableView indexPathsForRowsInRect:CGRectMake(0, targetContentOffset->y, _titleTableView.frame.size.width, _titleTableView.frame.size.height)];
NSMutableArray *arr = [NSMutableArray arrayWithArray:temp];
if (velocity.y<0) { // 上滑
NSIndexPath *indexPath = [temp lastObject];
if (indexPath.row+33) {
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row-3 inSection:indexPath.section]];
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row-2 inSection:indexPath.section]];
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:indexPath.section]];
}
}
[needLoadArr addObjectsFromArray:arr];
}
}

相应的,每次开始拖动的时候去清空数组。还有种情况,如果界面上有显示空白cell的时候突然手动停止滚动呢?

1
2
3
4
5
6
7
8
9
10
11
12
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
[needLoadArr removeAllObjects]; // 清空数组
// 取到当前界面上能显示的indexPaths,判断是否有隐藏
NSArray *indexpaths = [_titleTableView indexPathsForVisibleRows];
UITableViewCell *firstCell = [_titleTableView cellForRowAtIndexPath:indexpaths.firstObject];
UITableViewCell *lastCell = [_titleTableView cellForRowAtIndexPath:indexpaths.lastObject];
// 在当前可见的区域中,第一个cell或者最后一个cell是隐藏状态,那么重新加载可见区域内的cell
if (firstCell.isHidden == true || lastCell.isHidden == true) {
[_titleTableView reloadRowsAtIndexPaths:indexpaths withRowAnimation:UITableViewRowAnimationNone];
}
}
也可以把判断的代码写在scrollView停止滚动监听方法里,但是个人觉得没必要,因为这种情况必定是手动触碰去停止的,这里处理没问题

数据源方法:

1
2
3
4
5
6
7
8
if (needLoadArr.count > 0) {
if (![needLoadArr containsObject:indexPath]) {
// NSLog(@"该cell是快速滑动中的cell,所以隐藏");
cell.hidden = true;
return cell;
}
}
cell.hidden = false; // 正常显示的cell

附上:按需加载参考 VVeboTableViewDemo: 这位前辈在tableView优化上做到了极致。

  • 总结
    tableView性能优化的方式有很多,但不是所有的我们都需要。比如不是必需要显示的界面,预先计算行高就是浪费(用户流量)。应适时而定。站在用户体验的角度开发才是好的伐码猿。

UITableView重要属性图解

UITableView的重要属性.gif


☕️

☕️

期待


  • 如果在阅读过程中遇到 error || new ideas,希望你能 messages 我,我会及时改正谢谢。
  • 点击右上角的 喜欢 和 订阅Rss 按钮,可以收藏本仓库,并在 Demo 更新时收到邮件通知。
❄︎ 本文结束    感谢简阅 ^_^. ❄︎

本文标题:iOS UI控件详解—「UITableView表格视图」

文章作者:寄己的路

原始链接:https://sunyonghui.github.io/iOSUI/UITableView.html

版权声明: 署名-非商业性使用-禁止演绎 4.0 国际 本博客所有文章除特别声明外均为原创,转载务必请「注明出处链接(可点击) - 作者」,并通过E-mail等方式告知,谢谢合作!