UITableView 可以说是 UIKit 中最重要的一个组件,用来展示数据列表,还可以灵活使用进行页面的布局。UITableView 的使用遵循 MVC 模式,数据模型(NSObject)、视图(UIView)和控制器(UITableViewController)分离。UITableView 继承自 UIScrollView,可上下滑动,可以作为跟视图也可以作为子视图组件。
reuseIdentifier 顾名思义是一个复用标识符,是一个自定义的独一无二的字符串,用来唯一地标记某种重复样式的可复用 UITableViewCell,系统是通过 reuseIdentifier 来确定已经创建了的指定样式的 cell 来进行复用,iOS 中表格的 cell 通过复用来提高加载效率,因为多数情况下表格中的 cell 样式都是重复的,只是数据模型不同而已,因此系统可以在保证创建足够数量的 cell 铺满屏幕的前提下,通过保存并重复使用已经创建的 cell 来提高加载效率和优化内存,避免不停地创建和销毁 cell 元素。
UITableViewCell 的复用原理其实很简单,可以通过下面一个简单的例子来理解:
首先在开发中我们在 UITableViewController 类中写 cell 复用代码的最基本模板会像下面这样:
- /**
- * 可复用cell制作
- */
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
- // 定义cell重用的静态标志符
- static NSString *cell_id = @"cell_id_demo";
- // 优先使用可复用的cell
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cell_id];
- // 如果要复用的cell还没有创建,则创建一个供之后复用
- if (cell == nil) {
- // 新创建cell并使用cell_id复用符标记
- cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cell_id];
- }
- // 配置cell数据
- cell.textLabel.text = [NSString stringWithFormat:@"Cell%i", countNumber];
- // 其他cell设置...
- return cell;
- }
代码这样写的原因是通过调用当前 tableView 的 dequeueReusableCellWithIdentifier 方法看指定的 reuseIdentifier 是否有可以重复使用的了,如果有则会返回可复用的 cell,cell 就绪之后便可以开始更新 cell 的数据;如果还不可复用,则返回 nil,然后会进入后面的 if 语句,此时创建新的 cell 并对其设置 cell 样式标记 reuseIdentifier。注意上面的 if 语句并不是只要执行一次创建一次新的 cell 就完成任务,然后之后全部重复利用新创建的那一个 cell,这是对 cell 复用机制的误解。事实是要创建足够数量的可覆盖整个 tableView 的可复用 cell 之后才会开始复用之前的(UITableView 中有一个 visiableCells 数组保存当前屏幕可见的 cell,还有一个 reusableTableCells 数组用来保存那些可复用的 cell),这个我们用下面的测试来验证。
如何简洁清楚的展示 UITableViewCell 的复用机制呢?这里的方法是创建最基本的文本 cell,并创建一个 cell 创建计数器,每次新创建 cell 计数器加 1 并显示在 cell 上,如果是复用的 cell 则会显示是复用的哪一个 cell,测试代码如下:
- /**
- * 分区个数设置为1
- */
- - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
- return 1;
- }
- /**
- * 创建20个cell,保证覆盖并超出整个tableView
- */
- - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
- return 20;
- }
- /**
- * cell复用机制测试
- */
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
- // 定义cell重用的静态标志符
- static NSString *cell_id = @"cell_id_demo";
- // 计数用
- static int countNumber = 1;
- // 优先使用可复用的cell
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cell_id];
- // 如果要复用的cell还没有创建,则创建一个供之后复用
- if (cell == nil) {
- // 新创建cell并使用cell_id复用符标记
- cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cell_id];
- // 计数器标记新创建的cell
- cell.textLabel.text = [NSString stringWithFormat:@"Cell%i", countNumber];
- // 计数器递增
- countNumber++;
- }
- return cell;
- }
运行在 iPhone5S 设备上(UITableViewController 作为跟控制器,tableView 覆盖整个屏幕),20 个 cell 显示结果依次为:
Cell1、Cell2、Cell3、Cell4、Cell5、Cell6、Cell7、Cell8、Cell9、Cell10、Cell11、Cell12、Cell13、Cell14、Cell1、Cell2、Cell3、Cell4、Cell5、Cell6
可以看出一共创建了 14 个 cell,其中整个屏幕可显示 13 个 cell,系统多创建一个的原因是保证在表格滑动显示半个 cell 时仍然能覆盖整个 tableView。之后的 6 个 cell 就是复用了开始创建的那 6 个 cell 了。这样 UITableViewCell 复用的基本机制就很清楚了,另外还会有 reloadData 或者 reloadRowsAtIndex 等刷新表格数据的情况,可能会伴随新的 cell 创建和可复用 cell 的更新,但也是建立在基本复用机制的基础之上的。
可以,相当于视图以及视图控制器的嵌套,视图可以添加子视图,视图控制器也可以添加子控制器。这么问应该是因为这种情况有时会用到而且很重要,因为有一点容易被忽视,就是将子视图添加到了父视图却忘记将对应的控制器作为子控制器添加到父控制器,导致子视图能显示但是不能响应(没有对接好控制器)。例如在当前视图上放一个小尺寸的表格组件,也就是在 UIViewController 上添加一个 UITableViewController 子控制器及其子 view:
- // 假设有三个视图控制器,一个作为父控制器,两个作为子控制器
- UIViewController *superVC = [[UIViewController alloc]init];
- UITableViewController *subVC1 = [[UITableViewController alloc]init];
- UITableViewController *subVC2 = [[UITableViewController alloc]init];
- // 将子视图控制器添加到父视图控制器(要注意调整子视图的尺寸和位置合理显示,这里忽略)
- [superVC.view addSubview:subVC1.view];
- [superVC addChildViewController:subVC1];
- [superVC.view addSubview:subVC2.view];
- [superVC addChildViewController:subVC2];
- // 子视图控制器的移除有对称的方法,但只能是子视图控制器主动从父视图控制器中移除
- [subVC1.view removeFromSuperview];
- [subVC1 removeFromParentViewController];
- [subVC2.view removeFromSuperview];
- [subVC2 removeFromParentViewController];
此外要注意和 presentViewController 函数添加子视图控制器的区别,上面手动添加子视图控制器是可以自由调整子视图的 frame 的(包括子视图位置和尺寸),而 presentViewController 是用于页面切换,切换后的子页面会覆盖整个屏幕而不可以自由调整子页面位置和尺寸,对称的子视图控制器移除方法为 dismissViewControllerAnimated:
- // 显示子视图控制器,completion后的代码块如果不为空添加结束后会触发
- [[parentVC presentViewController:childVC animated:NO completion:nil];
- // 移除子视图控制器,completion后的代码块如果不为空添加结束后会触发
- [childVC dismissViewControllerAnimated:NO completion:nil];
多个数据源是完全可以的,关键是如何关联,问题的重点是如何处理,因为将数据源(Model)和 tableview 视图(View)的对接工作是程序员完成的,因此数据源的多少没有根本影响。处理上可以分开依次对接,也可以通过数据的集合操作先将数据整理合并成一个数据源然后对接。
例如:一个表格中的每个 cell 显示的是一个人的基本信息,为了简单这里假设只有一个头像和一个姓名。假设有两个数据源,一个数据源是头像的 url 数组,一个是姓名的字符串数组,对接时完全可以分开在 cell 数据回调中对接,也可以将两个数组合并然后对接。
合并数据用到的数据模型:
- @interface Model : NSObject
- @property (nonatomic,copy) NSString *name; // 姓名
- @property (nonatomic,copy) NSString *url; // 图片
- @end
数据源缓冲器:
- // 数据源
- @property (nonatomic, strong)NSArray *name_datasource;
- @property (nonatomic, strong)NSArray *url_datasource;
- @property (nonatomic, strong)NSMutableArray *datasource;
处理多数据源:
- /**
- * 请求数据
- */
- - (void)request {
- // 姓名数据源
- _name_datasource = @[@"张三", @"李四", @"小明", @"小李"];
- _url_datasource = @[@"male", @"male", @"male", @"male"];
- // 合并数据源
- for (int i; i<_name_datasource.count; i++) {
- Model *model = [[Model alloc]init];
- model.name = _name_datasource[i];
- model.url = _url_datasource[i];
- [_datasource addObject:model];
- }
- }
数据对接:
- /**
- * cell数据回调
- */
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
- static NSString *identifier = @"identifier";
- // 自制cell组件
- AccountCell *cell = [[AccountCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
- /** 多数据源分开对接:**/
- // 头像
- [cell.avatar setImage:[UIImage imageNamed:_url_datasource[indexPath.row]]];
- // 姓名
- cell.name.text = _name_datasource[indexPath.row];
- // 或者:
- /** 数据源合并后对接**/
- // 取出对应数据模型
- Model *model = _datasource[indexPath.row];
- // 头像
- [cell.avatar setImage:[UIImage imageNamed:model.url]];
- // 姓名
- cell.name.text = model.name;
- return cell;
- }
UITableView 的滚动优化主要在于以下两个方面:
来源: