前言
- Nib是什么?
Nib就是.xib文件:一个可视化的UI界面文件,它记录了一个UI组件(例如一个表格单元格Cell)的界面布局信息,可以在interfaceBuilder中创建
objective-c
[UINib nibWithNibName:@"CustomCell" bundle:nil]
这段代码的意思就是加载一个名叫CustomCell.xib的XIB文件,把它变成UINib对象么,准备好以后注册给其他对象使用
参数介绍 :
1.@"CustomCell":XIB文件名,不可以带.xib后缀
- bundle:nil :默认从主包加载
介绍
cell复用和自定义cell是在开发iOS应用时常见的优化技巧和定制需求,cell复用是UITableView或UIColletionView的一个重要‼️的优化机制。当用户滚动这些视图时,只有少量可见的cell会被创建和显示出来,对于那些不可见的cell,徐系统会将他们缓存起来以备将来使用。当用户在滚动屏幕的时候,屏幕上的cell只是所有数据的一小部分,当某个cell滚动出屏幕时,系统会将其放入一个队列中等待复用,当需要显示新的cell时,系统会首先检查这个队列,看是否有可以复用的cell,如果有就直接使用,如果没有才会创建新的cell。在实现cell的复用时,需要给cell设定不同的复用标识符(reuse identifier),然后在需要新的cell时,使用这个标识符去请求,如果队列中有可复用的cell,系统就会返回一个,否则就会创建新的cell,标识符的设定,使得我们可以为不同类型的cell设定不同的复用标识符,从而在同一个表视图或者集合视图中使用多种类型的cell。
复用原理
当创建UITableView并加载数据时,系统会实现如下操作
-
创建缓存结构:
- 一个字典:用于缓存cell的类与标识符的关系
- 一个数组:记录每个section的可见行信息
- 一个可变集合mutableSet:专门存放可以复用的cell
-
加载首屏cell(n + 1)个
- 他会调用
objc[dataSource tableView:cellForRowAtIndexPath:]
- 并通过
objective-c[tableView dequeueReusableCellWithIdentifier:@"MyCellID"]
去查看是否有可复用的cell?
-
cell滚出屏幕时,系统就会把他放进reuse pool中,而不是释放掉
-
向下滚动时,取出reuse pool 的cell
UITableView中cell复用方式
手动(非注册)
- 什么是非注册类型的?
非注册类型 = 没有使用registerClass:或registerNib:来注册cell,而是手动判断并创建的方式
- 使用方式
objective-c
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = @"MyCellID";
// 从复用池中获取
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
// 如果没有可复用的 cell,则创建一个新的
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
// 只初始化一次的配置可以写在这里
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
// 每次都要重新配置内容
cell.textLabel.text = [NSString stringWithFormat:@"Row %ld", indexPath.row];
return cell;
}
- 原理分析
UITableview会从内部的复用池寻找标识符为identifier的cell,如果没有可复用的cell,就返回nil,此时需要手动创建一个新的并指定相同的reuseIdentifier,系统会在滚动后自动将cell回收到复用池中
- 使用场景推荐
- 完全动态的cell类型
objective-c
NSString *identifier = [NSString stringWithFormat:@"Section_%ld", indexPath.section];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier];
}
目的:每个section使用一个不同的复用标识符
自动(注册类型)
- 什么是注册类型?
注册类型 = 提前告诉UITableView或UICollectionView:会使用哪一种cell(类或nib)来复用
注册后,系统会在你调用 dequeueReusableCellWithIdentifier:forIndexPath:
时,自动为你创建或复用cell,不再需要手动写 if (!cell)
。
- 注册方式
- 注册类
objective-c
[tableView registerClass:[MyCustomCell class] forCellReuseIdentifier:@"MyCellID"];
告诉系统会使用MyCustomCell这个类作为cell,reuseIdentifier是"MyCellID"
这个类必须是UITableViewCell的子类,且支持用init方法初始化,纯代码模式,不依赖.xib文件
适用于cell布局不复杂,可以通过AutoLayout或frame代码快速搭建,不需要使用Interface Builder
- 注册XIB(nib文件方式)
objective-c
UINib *nib = [UINib nibWithNibName:@"MyCustomCell" bundle:nil];
[tableView registerNib:nib forCellReuseIdentifier:@"MyCellID"];
告诉系统会使用一个名为MyCustomCell.xib的nib文件创建cell
nib里要设置cell的class是自定义类的UITableViewCell子类
- 如何使用已注册的cell?
objective-c
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyCellID" forIndexPath:indexPath];
// 配置 cell 的内容
cell.titleLabel.text = self.dataArray[indexPath.row];
return cell;
}
自定义cell的实现
如果要实现自定义cell需要实现两个协议
UItableViewDelegate和UITableViewDataSource
前者:
objective-c
//即将显示道屏幕上时调用
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
//当某个section的Header即将显示时调用
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section API_AVAILABLE(ios(6.0));
//某个 section 的 Footer 即将显示时调用
- (void)tableView:(UITableView *)tableView willDisplayFooterView:(UIView *)view forSection:(NSInteger)section API_AVAILABLE(ios(6.0));
//当某个cell即将滑出屏幕并结束显示时调用
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath API_AVAILABLE(ios(6.0));
//某个header滑出屏幕后调用
- (void)tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:(NSInteger)section API_AVAILABLE(ios(6.0));
//某个footer滑出屏幕后调用
- (void)tableView:(UITableView *)tableView didEndDisplayingFooterView:(UIView *)view forSection:(NSInteger)section API_AVAILABLE(ios(6.0));
//返回某一行的高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
//返回每个section的header高度
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
//设置每个section的footer高度
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;
后者:
objective-c
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
使用原因:
- 个性化设计需求,当系统提供的单元格样式无法满足应用的设计需求时,就需要通过自定义单元格来实现特定的界面和布局
- 复杂的数据展示:如果需要在单元格中显示复杂的数据结构(如图文混排、动态控件集合等),使用自定义单元格可以更灵活的控制数据的展示方式
- 优化性能:当列表视图中需要展示的单元格类型非常多样或者数据加载非常复杂时,通过精心设计的自定义单元格可以有效的提高滚动和渲染的性能
在自定义cell时,我们需要新建一个UITableViewCell类,在这个类的h文件中添加我们所需要的相关控件,在m文件中重写初始化initWithStyle:reuseldenifier:方法,在该方法中,首先使用父类的init方法初始化,以创建一个合法的cell,然后手动为该self的各个属性赋值,最后返回self即可
注意:在需要将自定义控价先添加道self.contentView上,这个contentView就是cell的子视图,是内容视图
- 自定义cell
objective-c
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface MyCustomCell : UITableViewCell
@property(nonatomic, strong) UILabel* titleLabel;
@end
NS_ASSUME_NONNULL_END
objective-c
#import "MyCustomCell.h"
@implementation MyCustomCell
//这是UITableView的指定初始化方法
/*
参数讲解
参数一:cell的样式
参数二:用于cell的复用机制,标记这个cell的身份
*/
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
//调用父类的UITableView的构造方法,初始化基础内容,必须写,否则无法获得一个合法的cell实例
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(15, 10, 300, 30)];
self.titleLabel.textColor = [UIColor orangeColor];
//contentView是UITbableCell内部的容器视图,应该把所有UI元素加到它上面,而不是直接加到cell本身
[self.contentView addSubview:self.titleLabel];
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
@end
- 实现
objective-c
#import "ViewController.h"
#import "MyCustomCell.h"
@interface ViewController () <UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong) UITableView *tableView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.tableView registerClass:[MyCustomCell class] forCellReuseIdentifier:@"MyCellID"];
[self.view addSubview:self.tableView];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 20;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCustomCell* cell = [tableView dequeueReusableCellWithIdentifier:@"MyCellID"forIndexPath:indexPath];
cell.titleLabel.text = [NSString stringWithFormat:@"Row:%ld",indexPath.row];
return cell;;
}
@end
- 在实现文件中实现一个layoutSubViews方法来设置各个空间的位置