有时我们可能需要用UITableView通过自定义Cell的方法来展示多行多列的图片,实现类似与UICollectionView的效果。例如电子书等应用。这里我们来实现这个功能。
首先可以自定义一个UIView来代表Cell内的每一个Item,然后以每行三列为例,将其布局到每个cell上。
对这个自定义cell,暴露出一个NSArray属性groupedItem(已分好组的Item)表示有三个Item的数组,这样在TableView中只要cell.groupedItem = ……,在cell中将这三个Item拿出来,分别为View提供数据,让View展示数据即可。
因此关键就是将数据模型(Model)分成2维数组,每个子数组有三个元素。
当从网络上或本地获取所有数据保存到字典中后,可以对这个数组每三个元素加入到一个新数组,然后新数组加入到最终的二维数组。即
- <span style="font-size:14px;">NSMutableArray *tempArray = nil;
- for (int i = 0; i < self.allBooks.count; i++) {
- if (i % 3 == 0) {
- tempArray = [NSMutableArray arrayWithCapacity:3];
- [self.bookGroup addObject:tempArray];
- }
-
- [tempArray addObject:[self.allBooks objectAtIndex:i]];
- }</span>
allBooks代表保存所有数据的数组,bookGroup表示最终的二维数组。
分好组以后就可以在tableview数据源协议方法中为cell提供数据
- <span style="font-size:14px;">- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- NSString *cellId = @"CYZBookShelfViewCell";
- CYZBookCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
-
- if (cell == nil) {
- cell = [[CYZBookCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
- }
-
- cell.booksGroup = [self.bookGroup2D objectAtIndex:indexPath.row];
-
- return cell;
- }</span>
在Cell中定义一个数组来接收该行显示的三个Model
- <span style="font-size:14px;">@interface CYZBookCell : UITableViewCell
-
-
- @property (strong, nonatomic) NSArray *booksGroup;
-
- @end</span>
然后再initView时建立三个子视图并加入到cell.contentView中,分别为这三个view赋tag值以便在layoutSubview中取得。接着在layoutSubview中设置这三个view的位置、数据等信息。
- <span style="font-size:14px;"> for (int i = 0; i < self.booksGroup.count; i++) {
- CYZBookItemView *item = (CYZBookItemView *)[self.contentView viewWithTag:100 + i];
- item.frame = CGRectMake(kHorizontalEdge + (kHorizontalEdge + 80) * i, kVerticalEdge, 80, 150);
- item.hidden = NO;
- item.bookModel = [self.booksGroup objectAtIndex:i];
- [self addSubview:item];
-
-
- [item setNeedsLayout];
- [item layoutIfNeeded];
- }</span>
为什么要再layoutSubview中调用item的setNeedsLayout方法呢?这是因为Cell的复用问题,当cell复用时并不会调用item的layoutSubview方法,因此上拉tableview时展示出来的数据全是与之前重复的。所以我们需要手动让item重新布置子视图。
另一个由cell复用机制引发的问题:依旧是数据重复
我们的数据一般是从网上来的,故数据的数量并不确定,很有可能不是恰好为3的倍数。例如有40个,这是你会发现,本来最后一样应该只有一个数据,却出现了三个,并且后两个是重复的。这就是cell复用引发的另一个问题,为了解决这一问题,我们应该让无数据的item隐藏。怎么判断item有没有数据呢?在上述for循环中,self.booksGroup中保存了有数据的item,因此能进入for循环的即为有数据的。在这里让item显示。
因此,我们可以复写booksGroup的setter,让所有的数据默认隐藏。
- <span style="font-size:14px;">- (void)setBooksGroup:(NSArray *)booksGroup
- {
- _booksGroup = booksGroup;
-
- for (int i = 0; i < booksGroup.count; i++) {
- CYZBookItemView *item = (CYZBookItemView *)[self.contentView viewWithTag:100 + i];
-
-
- item.hidden = YES;
- }
- }</span>
总结一下:
1、我们需要一个自定义视图Item表示在每行cell中的每一列,item可以包含图片、lable……自定义视图,主要功能为展示数据,只要将model对象给他,他就能展示model中的数据。
2、同时还需要一个自定义cell来盛下item,每个cell可以容纳多列,只要传给他一组数组,里面包含这一行的每一列的model对象即可,然后cell将每一个model分别给每一个item。
3、在tableView或其他控制器中将所有数据分组,分成二维数组,每一维都是每一行cell中的所有model,然后在tableview数据源协议中将二维数组的每一个元素(一维数组)分别给每一行让其显示该行的数据。
4、注意在这一过程中由于cell复用引发的一系列数据重复的问题,解决方法:1)手动调用item的setNeedsLayout方法让item刷新。2)让没数据的item隐藏,有数据的item显示。