UITableView,UICollectionView

28 7月
  • UITableView
  • UICollectionView

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的区别是:

  1. UICollectionViewCell需要一个UICollectionViewLayout处理布局
  2. UICollectionViewCell继承自UICollectionReusableView负责处理cell复用的逻辑,但复用前先要register注册一下。
  3. 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

发表评论

电子邮件地址不会被公开。 必填项已用*标注