news 2026/5/9 1:30:11

UITableView

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
UITableView

UITableView

UITableView本质就是一个可复用的列表容器

数据 → 显示规则 → 渲染成一行一行的 cell

一.cell简介

核心组成

  • UITableView(表)
  • UITableViewCell(每一行)
  • dataSource(数据来源)
  • delegate(行为控制)

我们首先要实现自定义cell需要两个协议

UITableViewDelegate和UITableViewDataSource

前者的主要用于实现显示单元格,设置单元格的行高和对于制定的单元格的操作设置头视图和尾视图

后者主要设置TableView的section与row的数量

创建一个 UITableView

-(void)viewDidLoad{[superviewDidLoad];self.dataArr=@[@"A",@"B",@"C",@"D"];self.tableView=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain];self.tableView.dataSource=self;self.tableView.delegate=self;[self.view addSubview:self.tableView];}

dataSource协议实现

// 行数-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section{returnself.dataArr.count;}// 每一行内容-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{staticNSString*cellID=@"cell";UITableViewCell*cell=[tableView dequeueReusableCellWithIdentifier:cellID];if(!cell){cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];}cell.textLabel.text=self.dataArr[indexPath.row];returncell;}

Delegate(行为控制)

// 点击某一行-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath{NSLog(@"点击了第 %ld 行",indexPath.row);}// 行高-(CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath{return60;}// 组数-(NSInteger)numberOfSectionsInTableView:(UITableView*)tableView{return2;}// 每组行数-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section{return3;}

二.cell的复用机制

如果你创建了 1000 行数据,滚动时不会创建 1000 个 cell,而是只创建屏幕显示范围内的 cell + 少量缓存,来回复用

初始化的时候他会先创建cell的缓存字典 和 section的缓存array,以及一个用于存放复用cell的mutableSet(可变的集合)。并且它会去创建显示的(n+1)个cell,其他都是从中取出来重用。
当有cell滑出屏幕时,会将其放入到一个set中(相当于一个重用池),当UITableView要求返回cell的时候,datasource会先在集合中查找是否有闲置的cell,若有则会将数据配置到这个cell中,并将cell返回给UITabelView。 这大大减少了内存的开销。
因为我在滚动的过程中会出现一个将cell滚出屏幕外的时候,这时候如果我们一直创建cell的话,如果cell太多了就会出现一个内存开销过多的一个问题。所以我们要采用这个复用的方式来提高内存利用率

cell复用的两种不同方式

手动进行cell复用(非注册)

设置复用标识符:在创建Cell时,我们要给每一个Cell设置一个复用标识符,这个标识符通常是一个字符串,用来表示cell类型 在创建cell时,我们会把这个标识符作为参数传入

请求重用的cell:在需要显示新的cell时,我们会使用复用标识符去请求一个不再显示但还没被销毁的的cell。这个过程是通过调用UITableView的dequeueReusableCellwithIdentifier:来实现的,这个方法会返回一个可选类型的Cell,如果有可用的重用Cell,就会返回一个Cell,否则返回nil

配置Cell:无论是新创建Cell还是重用的Cell,都需要进行配置,以显示数据,配置Cell在tableView(:cellForRowAt:)方法中完成

dequeueReusableCellWithIdentifier·:意思是出列的可用的cell,即使用这个方法可以获取通过滚动创建过并放回对象池中的可以复用的cell对象

例如我们在dataSource协议实现中的代码

-(void)viewDidLoad{[superviewDidLoad];self.dataArr=@[@"A",@"B",@"C",@"D"];self.tableView=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain];self.tableView.dataSource=self;self.tableView.delegate=self;[self.view addSubview:self.tableView];}-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{staticNSString*cellID=@"cell";UITableViewCell*cell=[tableView dequeueReusableCellWithIdentifier:cellID];if(!cell){cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];}cell.textLabel.text=self.dataArr[indexPath.row];returncell;}
自动注册

使用cell的注册机制,在cell的复用时无需判空,在viewDidLoad先对需要复用的cell使用registerClass进行注册,然后在创建cell的函数中使用dequeueReusableCellWithIdentifier获取可复用的cell,如果没有可复用的cell,就自动利用注册cell时提供的类创建一个新的cell并返回

-(void)viewDidLoad{[superviewDidLoad];self.tableView=[[UITableView alloc]initWithFrame:self.view.frame style:UITableViewStyleGrouped];self.tableView.delegate=self;self.tableView.dataSource=self;self.dataArr=@[@"头像",@"名字",@"微信号"];[self.view addSubview:_tableView];[self.tableView registerClass:[UITableViewCell class]forCellReuseIdentifier:@"cell"];}-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{UITableViewCell*cell=[tableView dequeueReusableCellWithIdentifier:@"cell"forIndexPath:indexPath];cell.textLabel.text=self.dataArr[indexPath.row];returncell;}

二者对比

取 cell → 没有 → 返回nil→ 手动创建
取 cell → 没有 → 系统自动创建 → 返回

真正区别是:

“是否自动创建 cell”

上述代码的区别在于注册方法需要提前对我们要使用的cell类进行注册,如此一来就不需要在后续过程中对我们的单元格进行判空。
这是因为我们的注册方法:
(void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier API_AVAILABLE(ios(6.0));
在调用过程中会自动返回一个单元格实例,如此一来我们就避免了判空操作

引用的方法不同

(nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;
(__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0);
第一个 method 用在了非注册的方式里,第二个 method 用在了需要注册的方式里。经过验证,第一个 method 也可以用在注册的方式里,但是第二个 method 如果用于非注册的方式,则会报错崩溃:

自定义cell

系统自带的UITableViewCellStyleDefault

👉 只能放:

  • 一个标题
  • 一个副标题(有限)

而实际需求需要比较复杂的逻辑,这时自定义的cell才能满足我们的需求

自定义cell的本质就是:UITableView+子视图

所有UI必须加在cell.contentView

例如:

首先新创建一个UITableViewCell的子类,用属性定义自己需要的控件

@interfaceMyCell:UITableViewCell@property(nonatomic,strong)UIImageView*iconView;@property(nonatomic,strong)UILabel*tieleLabel;@property(nonatomic,strong)UILabel*subLabel;@property(nonatomic,strong)UIButton*btn;@end

其次用-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier创建cell

-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString*)reuseIdentifier{self=[superinitWithStyle:style reuseIdentifier:reuseIdentifier];if(self){//头像self.iconView=[[UIImageView alloc]initWithFrame:CGRectMake(10,10,50,50)];self.iconView.backgroundColor=[UIColor grayColor];[self.contentView addSubview:self.iconView];//标题self.tieleLabel=[[UILabel alloc]initWithFrame:CGRectMake(70,10,200,20)];self.tieleLabel.font=[UIFont boldSystemFontOfSize:16];[self.contentView addSubview:self.tieleLabel];//副标题self.subLabel=[[UILabel alloc]initWithFrame:CGRectMake(70,35,200,20)];self.subLabel.font=[UIFont systemFontOfSize:14];self.subLabel.textColor=[UIColor grayColor];[self.contentView addSubview:self.subLabel];//按钮self.btn=[UIButton buttonWithType:UIButtonTypeSystem];self.btn.frame=CGRectMake(300,20,60,30);[self.btn setTitle:@"点击"forState:UIControlStateNormal];[self.btn addTarget:selfaction:@selector(pressBtn)forControlEvents:UIControlEventTouchDown];[self.contentView addSubview:self.btn];}returnself;}-(void)pressBtn{NSLog(@"按钮被点击了");}

随后回到ViewController和一开始的那段代码差不多的流程

#import"ViewController.h"#import"MyCell.h"@interfaceViewController()<UITableViewDelegate,UITableViewDataSource>@property(nonatomic,strong)UITableView*tableView;@property(nonatomic,strong)NSArray*dataArr;@property(nonatomic,strong)NSArray*imageArr;@end@implementationViewController-(void)viewDidLoad{[superviewDidLoad];self.view.backgroundColor=[UIColor whiteColor];self.dataArr=@[@"左思源",@"源蛋",@"小蛋",@"小源",@"源思左"];self.imageArr=@[@"zsy1",@"zsy2",@"zsy3",@"zsy4",@"zsy5"];self.tableView=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain];self.tableView.dataSource=self;self.tableView.delegate=self;//设置代理[self.tableView registerClass:[MyCell class]forCellReuseIdentifier:@"cell"];//注册cell[self.view addSubview:self.tableView];}-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section{returnself.dataArr.count;}-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{MyCell*cell=[tableView dequeueReusableCellWithIdentifier:@"cell"forIndexPath:indexPath];//cell的复用cell.tieleLabel.text=self.dataArr[indexPath.row];cell.subLabel.text=self.dataArr[indexPath.row];cell.iconView.image=[UIImage imageNamed:self.imageArr[indexPath.row]];returncell;}-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath{NSLog(@"点击了 %@",self.dataArr[indexPath.row]);[tableView deselectRowAtIndexPath:indexPath animated:YES];}-(CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath{return70;}

需要注意的几个点是:

  1. 不要忘记设置两个协议的代理
  2. 注册cell,cell的复用
  3. 用数组存数据(也可以用model封装成一个类,不用创建多个数组)

在此附上两个协议的一些方法源码:

可以看到的是UITableViewDataSource的这两个方法是必须实现的:

-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath

其余的选择自己需要的实现就好

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/9 1:17:30

Need项目:将项目环境配置从文档升级为可执行规范

1. 项目概述&#xff1a;一个“需要”驱动的代码仓库在开源世界里&#xff0c;我们每天都会遇到数以万计的新项目。有些项目名字直白&#xff0c;一看就知道是做什么的&#xff1b;有些则充满诗意&#xff0c;让人浮想联翩。今天要聊的这个项目tuckerschreiber/need&#xff0c…

作者头像 李华
网站建设 2026/5/9 1:01:50

SD/TF/SD NAND/eMMC存储及插卡检测详解

概述本文档完整介绍SD卡、TF卡&#xff08;Trans-flash/MicroSD&#xff09;、SD NAND、eMMC四类嵌入式闪存存储介质的全部技术细节&#xff0c;包含命名溯源、介质关系、引脚信号定义、核心优缺点、适用场景、存储卡插入检测全方案及特殊CLK复用CD检测原理。一、存储介质基础定…

作者头像 李华
网站建设 2026/5/9 1:01:25

Markdown 快速入门:标题、列表和代码块怎么写

Markdown 快速入门&#xff1a;标题、列表和代码块怎么写这是一篇用于测试发布流程的简短示例文章&#xff0c;内容只演示 Markdown 中最常见的几种基础写法&#xff0c;包括标题、列表和代码块。一、标题怎么写Markdown 标题通常使用井号表示&#xff1a;# 一级标题 ## 二级标…

作者头像 李华