UICollectionView自定义布局

前言

UICollectionView由于可以custom layout的原因使其非常强大,一个layout能玩出花来。之前有一个非常有名的第三方库iCarousel,效果也很不错,但是由于在性能上还是稍差些。后来就慢慢被UICollectionView取代了。
进入正题,我做自定义layout的时候就是看raywenderlich上面的教程,我觉得非常好,就用raywenderlich的demo来记录一下。demo在这里

layout和data的关系

核心布局处理


自定义layout,有三个方法必须重写

  • func prepareLayout() 在这个方法里计算好每个Item的position和CollectionView的size并缓存
  • func collectionViewContentSize() -> CGSize 返回CollectionView的ContentSize
  • func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]?在这个方法里返回某个特定区域的布局的属性

计算布局的属性

获取cell各个控件的高度以及header的高度,使用代理

1
2
3
4
5
6
7
8
9
10
11
12
@objc protocol WeiouDetailCollectionViewLayoutDelegate {
Method to ask the delegate for the height of the image
func collectionView(collectionView:UICollectionView, heightForPhotoAtIndexPath indexPath:NSIndexPath , withWidth:CGFloat) -> CGFloat
Method to ask the delegate for the height of the annotation text
func collectionView(collectionView: UICollectionView, heightForAnnotationAtIndexPath indexPath: NSIndexPath, withWidth width: CGFloat) -> CGFloat


func collectionView(collectionView: UICollectionView, heightForShortAddrAtIndexPath indexPath: NSIndexPath, withWidth width: CGFloat) -> CGFloat

for header
func collectionViewHeightForHeadrPicture(collectionView: UICollectionView, withWidth width: CGFloat) -> CGFloat
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
override func prepareLayout() {
// 1. Only calculate once
if cache.isEmpty {

// 2. Pre-Calculates the X Offset for every column and adds an array to increment the currently max Y Offset for each column
// 每列宽度
let columnWidth = contentWidth / CGFloat(numberOfColumns)
var xOffset = [CGFloat]()
// 其实就是xOffset就是两个,都是固定的.
for column in 0 ..< numberOfColumns {
xOffset.append(CGFloat(column) * columnWidth )
}
var column = 0
var yOffset = [CGFloat](count: numberOfColumns, repeatedValue: 0)

// 3. Iterates through the list of items in the first section
for item in 0 ..< collectionView!.numberOfItemsInSection(0) {

let indexPath = NSIndexPath(forItem: item, inSection: 0)

// 4. Asks the delegate for the height of the picture and the annotation and calculates the cell frame.
// 这个width是为了计算comment的长度的.
let width = columnWidth - cellPadding*2
let photoHeight = delegate.collectionView(collectionView!, heightForPhotoAtIndexPath: indexPath , withWidth:width)
let annotationHeight = delegate.collectionView(collectionView!, heightForAnnotationAtIndexPath: indexPath, withWidth: width)
let height = cellPadding + photoHeight + annotationHeight + cellPadding
let frame = CGRect(x: xOffset[column], y: yOffset[column], width: columnWidth, height: height)
let insetFrame = CGRectInset(frame, cellPadding, cellPadding)

// 5. Creates an UICollectionViewLayoutItem with the frame and add it to the cache
let attributes = PinterestLayoutAttributes(forCellWithIndexPath: indexPath)
attributes.photoHeight = photoHeight
attributes.frame = insetFrame
cache.append(attributes)

// 6. Updates the collection view content height
contentHeight = max(contentHeight, CGRectGetMaxY(frame))
yOffset[column] = yOffset[column] + height

column = column >= (numberOfColumns - 1) ? 0 : ++column
}
}
}

还是上图吧,一图胜千言

在layout中,没有列的概念,就是很机械的按照规则排列下去,主要Y值的计算。虚拟了一个列的概念
column = column >= (numberOfColumns - 1) ? 0 : ++column
理解一下

返回布局的ContentSize

1
2
3
4
5
//需要返回的是整个collectionView高度,包括header, footer, cell

override func collectionViewContentSize() -> CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}

重写layoutAttributesForElementsInRect(_:)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

var layoutAttributes = [UICollectionViewLayoutAttributes]()

if CGRectIntersectsRect(headerAttributes.frame, rect ) {
layoutAttributes.append(headerAttributes)
}


// Loop through the cache and look for items in the rect
for attributes in cache {
if CGRectIntersectsRect(attributes.frame, rect ) {
layoutAttributes.append(attributes)
}
}
return layoutAttributes
}

有header footer的话一定要加上!!!

关于获取高度的代理方法

主要是图片,纯文本,富文本的高度,方法Google一下就知道了,很简单。富文本的高度计算坑比较大,另开一篇说吧。。。

更新约束

最后,在自定义的cell,header里调用

1
-(void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes

更新约束,然后,大功告成!