- UITableView
- UICollectionView
- IGListKit
UITableView
列表是常见的UI组件,特点是样式比较统一,可垂直滚动,Cell可以复用等特点。UITableView只负责视图部分的展示,数据管理通过delegate设计模式让开发者实现UITableViewDataSource,里面有两个@required方法:
@protocol UITableViewDataSource<NSObject> @required // UITableView中有多少个cell - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section; // 设置具体的UITableViewCell - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; @optional ... @end
创建具体的UITableViewCell时,除了可以使用系统提供的几种默认样式UITableViewCellStyle外,还可以设置reuseIdentifier作为cell的标识。当cell在从可视区域内滚动出去后,系统会将这个cell放入回收池。即将要进入可视区域的cell会先去回收池看能否取到已创建的cell,能取到就复用,这样可以避免滚动时不停地创建新cell,节省资源,让cell的数量始终维持在一定区间内。
cell都有自己的NSIndexPath,可以用section分组,每个section里有各自的row号。
除了实现dataSource这个delegate外,通常还需要实现delegate,即UITableViewDelegate。该delegate了提供滚动时cell出现和消失的时机,cell的各种行为回调(如点击,删除等),还可以设置UITableViewCell的高度,headers,footers的设置。
@protocol UITableViewDelegate<NSObject, UIScrollViewDelegate> @optional ... // 设置UITableViewCell的高度 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath; // 点击cell后的回调 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath; @end
例如:
// 实现UITableViewDataSource, UITableViewDelegate两个delegate @interface ViewController ()<UITableViewDataSource, UITableViewDelegate> @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; super.view.backgroundColor = [UIColor whiteColor]; UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds]; tableView.dataSource = self; // 设置UITableViewDataSource为当前view tableView.delegate = self; // 设置UITableViewDelegate为当前view [self.view addSubview:tableView]; } // UITableView中有多少个cell - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 20; } // 设置具体的UITableViewCell - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // UITableView的回收池是否有可复用的同类型的cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"id"]; if (!cell) { // 没有可复用的就创建新 cell cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"id"]; } cell.textLabel.text = [NSString stringWithFormat:@"%@", @(indexPath.row)]; // 替换符%@:NSString实例 cell.detailTextLabel.text = @"子标题"; cell.imageView.image = [UIImage imageNamed:@"icon.bundle/video@2x.png"]; return cell; } // 设置UITableViewCell的高度 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 100; } // 点击cell后的回调 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { UIViewController *vc = [[UIViewController alloc] init]; vc.title = [NSString stringWithFormat:@"%@", @(indexPath.row)]; [self.navigationController pushViewController:vc animated:YES]; } @end
UICollectionView
如首页双列瀑布流就无法用UITableView实现,因为左右高度不同,无法放到同一行cell里。UICollectionView支持纵向横向布局更加灵活,同样只负责展示,数据是基于UICollectionViewDataSource,UICollectionViewDelegate驱动的,功能参照UITableViewDataSource,UITableViewDelegate。
UICollectionViewDataSource,里面有两个@required方法:
@protocol UITableViewDataSource<NSObject> @required // UICollectionView中有多少个cell - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section; // 设置具体的UICollectionViewCell - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath; @optional ... @end
UICollectionView已经没有row的概念了,只有item,一行可以展示多个item。
UICollectionViewCell和UIViewController的区别是:
- UICollectionViewCell需要一个UICollectionViewLayout处理布局
- UICollectionViewCell继承自UICollectionReusableView负责处理cell复用的逻辑,但复用前先要register注册一下。
- UICollectionViewCell没有默认的样式,不以行为设计基础,只有contentView,backgroundView。
UICollectionView能实现纵向横向灵活布局的关键就是有UICollectionViewLayout,它是个抽象类用于生成布局信息,可以对每个cell进行自定义的frame和size的处理。
系统为我们提供了默认的UICollectionViewFlowLayout流式布局(继承自UICollectionViewLayout),但设置它的属性是对所有cell生效的,如果要对指定的cell应用特殊的布局,需要实现UICollectionViewDelegateFlowLayout
@interface TestViewController ()<UICollectionViewDataSource, UICollectionViewDelegate> @end @implementation TestViewController -(instancetype) init { self = [super init]; if(self) { } return self; } - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; // // 系统默认的流式布局的 layout UICollectionViewFlowLayout *flowlayout = [[UICollectionViewFlowLayout alloc] init]; flowlayout.minimumLineSpacing = 10; // 行间距 flowlayout.minimumInteritemSpacing = 10; // 一行中每个 item 的间距,这个间距是最小值,实际会随着屏幕尺寸的不一样而变大 flowlayout.itemSize = CGSizeMake((self.view.frame.size.width - 10)/2, 300); // 创建 UICollectionView 时,需要一个 FlowLayout 和 屏幕大小 UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:flowlayout]; collectionView.dataSource = self; collectionView.delegate = self; // 要先注册,然后才能复用回收池中的 cell [collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"UITableViewCell"]; [self.view addSubview:collectionView]; } // UICollectionView中有多少个cell - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return 20; } // 设置具体的UICollectionViewCell - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { // 如果回收池里没有可复用的 cell,会去创建 cell UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"UITableViewCell" forIndexPath:indexPath]; cell.backgroundColor = [UIColor blueColor]; return cell; } // 根据indexPath为具体的cell定制布局 - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { if(indexPath.item%3 == 0) { return CGSizeMake(self.view.frame.size.width, 100); } return CGSizeMake((self.view.frame.size.width - 10)/2, 300); } @end
IGListKit
现在列表页的样式越来越复杂,有搜索出来的头图样式,有搜索出来的常规样式,有广告样式等。如果在tableView里对每个cell(根据indexPath)单独处理,会造成代码膨胀,而且每个cell高度不同的话,会导致计算开销大,页面卡顿。除了合理设计业务逻辑外,还可以:
1.代码层面优化:分离出Adapter设置delegate来缩减controller中的代码体积,而且能将业务逻辑收敛在Adapter中。
2.系统层面优化:可以缓存/预加载cell的高度和子元素的位置,不用每次都重新计算。因为当我们创建cell的数据源时,编译器并不是先创建cell再定cell的高度,而是先根据内容确定每一个cell的高度,高度确定后,再创建要显示的cell。滚动时,每当cell快进入屏幕前都会计算高度,提前估算高度告诉编译器,编译器知道高度后,紧接着就会创建cell。提前计算并预置好高度,可以提升table性能。对于类似上来没有评论计算并缓存好高度,等添加评论后高度变化时,要及时更新缓存。
Instagram推出的开源项目IGListKit就是这个设计思路:抽离出了Adapter层,将不同业务逻辑封装到不同SectionController中,在SectionController中(相同业务逻辑)中实现展示的具体cell
用法很简单,见官网