快乐的鲸鱼

(译)UITableViewCell中嵌套UICollectionView

2016/05/31

说在前面

在写的一个App用到了UITableViewCell中嵌套UICollectionView的设计,原以为会很简单,但是后来发现还是问题多多的,例如DataSource的设置问题,还有我对重用中的一些理解的混乱,巴拉巴拉巴拉。所以就找到了篇的原文。

原文:Putting a UICollectionView in a UITableViewCell

正文

所以,你希望将collection view放进一个table view cell里面是吧?这听起来很简单对吧?好,做好这件简单的事情需要一点点准备工作。我们希望每个类结构清晰各司其职,那么UITableViewCell不能充当UICollectionViewDataSourcedelegate(因为不分开的话会非常糟糕)。你可以下载这里的示例代码。

我们打算构建一个视图,结构如下图。每一个UITableViewCell会嵌套一个UICollectionView。(为了某些原因,我们到的collection view 会是自定义的子类。)每一个collection view 装着确定数目的cell,cell的数目由DataSource给出。

view的结构图

每一个 collection view与table view有同样的Datasourcedelegate,自定义UICollectionView的子类也是我们需要做的。

diagram of DataSource&delegate

上面的图示向你展示了这个demo较为复杂的部分,如何将view controller看成UICollectionViewdelegate。我们上面说到的子类继承了UICollectionView,我们给它一个index属性(property)。一半情况下,继承UICollectionView是比较少见的,不过在这里还是可以接受这种做法的。

将collection view 加到cell里面还是太早了,我们还需要做些工作。我们在cell 特定的初始化器中用标准的UICollectionViewFlowLayout实例化AFCollectionView的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (id)initWithStyle:(UITableViewCellStyle)style 
reuseIdentifier:(NSString *)reuseIdentifier
{
if (!(self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) return nil;

UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10);
layout.itemSize = CGSizeMake(44, 44);
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.collectionView = [[AFIndexedCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:CollectionViewCellIdentifier];
self.collectionView.backgroundColor = [UIColor whiteColor];
self.collectionView.showsHorizontalScrollIndicator = NO;
[self.contentView addSubview:self.collectionView];

return self;
}

我们在layoutSubviews里面调整collection view 的frame去适应并且填满cell。

1
2
3
4
5
6
-(void)layoutSubviews
{
[super layoutSubviews];

self.collectionView.frame = self.contentView.bounds;
}

接下来我们将在view colltroller里面设置我们的model。我们用多种UIColor来设置我们的模型,简单又明显。每一个cell将显示各不相同而且随机的颜色。

这个model是为了反映每个table view 和每个collection view的效果。我们首先用一个数组存储多个对象,每个对象反映一个table cell。然后这些对象它们自己也是数组,每个数组里的对象反映的是collection view cell里面的内容。

1
2
3
4
5
-(NSInteger)tableView:(UITableView *)tableView 
numberOfRowsInSection:(NSInteger)section
{
return self.colorArray.count;
}

接下来我们去实现UICollectionViewDataSource的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-(NSInteger)collectionView:(AFIndexedCollectionView *)collectionView 
numberOfItemsInSection:(NSInteger)section
{
NSArray *collectionViewArray = self.colorArray[collectionView.index];
return collectionViewArray.count;
}

-(UICollectionViewCell *)collectionView:(AFIndexedCollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CollectionViewCellIdentifier forIndexPath:indexPath];

NSArray *collectionViewArray = self.colorArray[collectionView.index];
cell.backgroundColor = collectionViewArray[indexPath.item];

return cell;
}

你会看到我们使用的index是collection view的属性,我们用它来查询到适合的model去配置我们的collection view cell。请注意,view controller不会存储任何指向collection view的引用,collection view们只属于UITableViewCell们。这是个好消息,因为它们都会被重用,这会节省我们的内存资源。

但是我们从哪里得到index?这也是我们需要做的。

1
2
3
4
-(void)tableView:(UITableView *)tableView willDisplayCell:(AFTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
[cell setCollectionViewDataSourceDelegate:self index:indexPath.row];
}

剩下要做的事情就是去“记住”每个collection view在其所在的table cell将要不显示(被滑动离开屏幕)时候的collection view内容的偏移量(collection view的content被滑动的距离),这样子就可以达到“记住”用户在不同table cell滑动collection view后的位置不在初始位置的效果了。我们这里利用NSMutableDictionary来记住我们内容的偏移量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-(void)tableView:(UITableView *)tableView 
willDisplayCell:(AFTableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
[cell setCollectionViewDataSourceDelegate:self index:indexPath.row];
NSInteger index = cell.collectionView.index;

CGFloat horizontalOffset = [self.contentOffsetDictionary[[@(index) stringValue]] floatValue];
[cell.collectionView setContentOffset:CGPointMake(horizontalOffset, 0)];
}

-(void)tableView:(UITableView *)tableView
didEndDisplayingCell:(AFTableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat horizontalOffset = cell.collectionView.contentOffset.x;
NSInteger index = cell.collectionView.index;
self.contentOffsetDictionary[[@(index) stringValue]] = @(horizontalOffset);
}

content offset

以上就是我们全文的主要内容啦。不需要很多的代码,但是做好它需要在编码前进行稍微的设计结构。你可以在Github下载完整的代码

最后还有作者的安利:)。真心希望你喜欢我的教程,我还推荐一下我的电子书UICollectionView: The Complete Guide。在书里面我会里里外外地向你介绍如何使用UICollectionView。你可以马上预定然后下载所以已经写出来的章节,心动不如行动。括弧笑。

CATALOG
  1. 1. 说在前面
  2. 2. 正文