首页 > 代码库 > [翻译]在objective c创建自定义collection view样式

[翻译]在objective c创建自定义collection view样式

创建自定义collection样式

苹果在ios6中新增了一个更加易于创建和管理复杂用户界面的类:collection view。在此之前ios6上面用于展示多项列表的是table view,虽然名称是表格但它展示信息的形式并不是表格形式,而是垂直列表,当然在实际设计展示垂直可滚动的文本型列表时非常有用,但是你会遇到许多垂直可滚动的列表所存在的局限性,比如不能水平滚动,每行只有一项单元格,没有复杂的样式,在ios6之前你想要做到这些事情,只能靠自定义扩展。
在ios6 平台介绍了一个新的类:collection view ,它拥有所有table view的功能,新增了许多展示信息的方式并且变得更加易用。
table view 的功能非常有限,而collection view 则可以进行更加灵活的设计。它可以按不同的形式展示表格内容:横向或垂直滚动,列表或表格或任何你想要的自定义样式,它还提供了非常灵活的样式定义去为每一个 collection view 设定不同的展现形式,苹果提供了一个简单的例子来说明这个样式功能将会满足许多人的需要,这个 flow layout 是基于每一行定义的布局,每一个单元都可以线性地展现在上一个单元后面,直到该行填充完之后,切换到下一行。这将会根据他的配置项来决定时垂直还是水平滚动,或者网格形式(每行有多个单元格)。

即使你的样式不那么简单,你仍然可以使用collection view。只需要提供自定义样式即可。本文将会提供一个创建自定义样式的例子,我不打算提供一个完整的collection view 例子或者样式, 苹果的手册和WWDC视频(205-219)已经提供了非常好的样例。本文将会基于一个已完成的例子进行自定义样式调整。

现在让我们来看看我们想要自定义的样式:一些单元格很大,而其他的会小一些。
img
要支持这个样式,我们需要继承并重写 UICollectionViewLayout 告诉collection 要如何去展现单元格。

首先我们来定义collection view 的大小。我们的样式将会展示12个单元格在一页,假如多于12个单元格,我们将会在按水平方向滚动展示。所以计算一页会有多少单元格展示是一件非常简单的事情,我们只需要根据collection view 的数据源(data source)提供的数据进行计算就可以知道我们总共需要展示多少页。我把这个 collectionVIewCOntenSize 方法重写了:

`- (CGSize)collectionViewContentSize
{

// Ask the data source how many items there are (assume a single section)
id<UICollectionViewDataSource> dataSource = self.collectionView.dataSource;
int numberOfItems = [dataSource collectionView:self.collectionView numberOfItemsInSection:0];

// Determine how many pages are needed
int numberOfPages = ceil((float)numberOfItems / (float)kNumberOfItemsPerPage);

// Set the size
float pageWidth = self.collectionView.frame.size.width;
float pageHeight = self.collectionView.frame.size.height;
CGSize contentSize = CGSizeMake(numberOfPages * pageWidth, pageHeight);

return contentSize;

}`

下一步我们要自定义每一页中需要展示的单元格样式,对于每一个单元格,我们的样式要处理的事情:

  • 这个单元格在第几页
  • 这个单元格在第几行(或者它的垂直偏移量是多少)
  • 这个单元格在该行第几位(或者它的水平偏移量是多少)
  • 它的大小

collection view 有两种方式获取样式的信息,当你需要在一个新的区域展示collection的时候,它将会向-layoutAttributesForElementsInRect:传递一个 rect 定义之后注册一个collection view。它也可以按照相对的布局信息,通过调用-layoutAttributesForItemAtIndexPath: 来返回每一个单元格需要样式信息。对于布局来说,我们也可以使用惰性计算而不必计算每一个元素的布局信息,例如:我们的应用会有大约100个单元格的布局,为了简单起见,我总是预先计算好布局的属性并且缓存起来,这样的处理最终被证明是一个很不错的性能优化方式。

根据苹果提供的文档,样式将会在调用这几个方法的时候被初始化:

  1. prepareLayout
  2. collectionViewContentSize
  3. layoutAttributesForElementsInRect

所以我们将会在初始化 -prepareLayout 方法的时候计算所有的样式信息。有非常多的方式可以计算每一个样式信息,但是在这里我将会使用一个简单的例子:

因为我知道每一页给定的单元格式12,所以我决定根据按页给予每一个元素定义不同的参数,我可以设置每一页的展示单元格为水平方式,当存在多个页面时,将会按照页面宽度去初始化collection view的宽度。这意味着我要在获得元素之后计算出正确的页数,这里我们知道每一页需要展示12个单元格,我可以将总数除以12来获得正确的页数。然后根据每一个单元格的水平和垂直偏移量来计算它的位置,这里可以看出来水平方向只有4个单元格,而其中三个比较小,所以我们可以使用很简单的switch语句来确定每一个单元格的样式参数,然后就可以根据这些值为每一个单元格创建一个布局属性对象。

`

  • (CustomFlowLayoutAttributes )layoutAttributesForItemAtIndex:(int)index
    {
    NSIndexPath
    path = [NSIndexPath indexPathForItem:index inSection:0];
    CustomFlowLayoutAttributes attributes = (CustomFlowLayoutAttributes )[super layoutAttributesForItemAtIndexPath:path];

    // Figure out what page this item is on
    int pageNumber = floor((float)index / (float)kNumberOfItemsPerPage);

    // Set the horizontal offset for the start of the page
    float pageWidth = self.collectionView.frame.size.width;
    float horizontalOffset = pageNumber * pageWidth;

    // Now, determine which position this cell occupies on the page.
    int indexOnPage = index % kNumberOfItemsPerPage;

    int column = 0;
    switch (indexOnPage) {

    case 0:
    case 3:
    case 5:
        column = 0;
        break;
    case 1:
    case 4:
    case 6:
    case 9:
        column = 1;
        break;
    case 2:
    case 7:
    case 10:
        column = 2;
        break;
    case 8:
    case 11:
        column = 3;
        break;
    default:
        column = 0;
        break;

    }

    int row = 0;
    switch (indexOnPage) {

    case 0:
    case 1:
    case 2:
        row = 0;
        break;
    case 3:
    case 4:
        row = 1;
        break;
    case 5:
    case 6:
    case 7:
    case 8:
        row = 2;
        break;
    case 9:
    case 10:
    case 11:
        row = 3;
        break;
    default:
        row = 0;
        break;

    }

    horizontalOffset = horizontalOffset + ((kColumnWidth + kPadding) * column);
    float verticalOffset = (kRowHeight + kPadding) * row;

    // finally, determine the size of the cell.
    float width = 0.0;
    float height = 0.0;

    switch (indexOnPage) {

    case 2:
        width = kLargeCellWidth;
        height = kLargeCellHeight;
        break;
    case 5:
        width = kColumnWidth;
        height = kMediumCellHeight;
        break;
    default:
        width = kColumnWidth;
        height = kRowHeight;
        break;

    }

    CGRect frame = CGRectMake(horizontalOffset, verticalOffset, width, height);
    [attributes setFrame:frame];

    return attributes;
    }`

这样定义样式计算的属性值之后,我们就可以很简单的通过向layoutAttributesForItemAtIndexPath: 或者 layoutAttributesForElementsInRect发送请求获取每一个单元格的属性。

这就是为使用 collection view 所需要自定义的样式,它将会允许collection view 在指定单元格的位置显示正确数目的单元格。这里只需要我们做一个简单的事情:继承并显示我们的单元格。就会获得三个不同大小的单元格框架(一些小的,一个很大并且占用两行的单元格和一个中等大小的)这三种不同的单元格就像我们代码所设定的一样显示。最后我们在每一个单元格类中定义单元格的内容,我们就可以得到自定义样式并将这些样式信息添加到每一个单元格属性中。

要做到这个我们只需要:
继承 UICollectionViewLayoutAttributes 去创建一个自定义的子类并将我们想要扩展的信息添加到子类中传递给我们的单元格(在这里我们用 cell 类型)。注意创建的时候要添加一个property, 我需要重写一个父类接口的 copyWithZone: 来保证添加的 property 能够使用NSCopying 协议去创建一个 UICollectionView的副本传递给单元格。

实现这个UICollectionViewLayoutAttriButes的方法,我们自定义的样式子类将会返回我们想要的样式属性。

`

  • (Class)layoutAttributesClass
    {
    return [CustomFlowLayoutAttributes class];
    }`

然后我只需要在我实现UICollectionVIewCell子类中计算附加的样式属性值和实现一些逻辑,就可以在接收这些布局参数之后,得到一个我需要的布局。

虽然苹果的文档(以及无数次提到的WWDC视频)已经非常描述得非常清楚。但是对大多数人来说,只要使用标准的流式布局就应该足够了,如果你发现在流式布局的情况下不能完全满足你的需求的时候,也许你需要创建一个自定义的布局,这只需要占用你一点点的工作时间就可以实现。

原帖来自:http://stripysock.com.au/blog/2013/2/21/creating-a-custom-collection-view-layout

[翻译]在objective c创建自定义collection view样式