转载

Custom Collection View Layouts(一)

Custom Collection View Layouts(一)

最近把 Ray 家的 Custom Collection View Layouts 系列视频刷了一遍,做一下记录

在开始前,先来看一下复习下基本知识:

Custom Collection View Layouts(一)

UICollectionView 向 UICollectionViewLayout 询问布局,而当询问过程发生时,layout 对象会创建 UICollectionViewLayoutAttributes 实例。一个 UICollectionViewLayoutAttributes 对象管理着一个对应的 item layout 相关信息(一对一关系)

来看一下第一部分的目标,我们要实现一个类似 Pinterest 的东西,有如下特性:

Custom Collection View Layouts(一)

一、Basic Layout

要实现上图的目标,我们先来解决布局的问题,为此要定义一个 UICollectionViewLayout 的子类,原因如下:

Custom Collection View Layouts(一)

自定义一个 UICollectionViewLayout 的子类 PinterestLayout ,一般在子类中要重写三个方法:

  1. override func collectionViewContentSize() -> CGSize 返回当前所以内容的 contentSize,不仅仅是当前可见的

    override func collectionViewContentSize() -> CGSize {     return CGSize(width: width, height: contentHeight) }
2. `override func prepareLayout()` 计算每一个 item 的 size,然后为每一个 item 创建对应的 attributes,并设置 frame,然后放到缓存中去  ```swift  override func prepareLayout() {   if cache.isEmpty {    // 每个 item 的宽度是固定的    let columnWidth = width / CGFloat(numberOfColumns)    var xOffsets = [CGFloat]()    for column in 0..<numberOfColumns {     xOffsets.append(CGFloat(column) * columnWidth)    }    var yOffsets = [CGFloat](count: numberOfColumns, repeatedValue: 0)    var column = 0    for item in 0..<collectionView!.numberOfItemsInSection(0) {     let indexPath = NSIndexPath(forItem: item, inSection: 0)     let height = delegate.collectionView(collectionView!, heightForItemAtIndexPath: indexPath)     let frame = CGRect(x: xOffsets[column], y: yOffsets[column], width: columnWidth, height: height)     let attributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)     attributes.frame = frame     cache.append(attributes)     contentHeight = max(contentHeight, CGRectGetMaxY(frame))     yOffsets[column] = yOffsets[column] + height     column = column >= (numberOfColumns - 1) ? 0 : ++column    }   }  }  ``` 3. `override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]?` 返回 rect 内所有 cells 和 views 的 **layout attributes**  ```swift  override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {   var layoutAttributes = [UICollectionViewLayoutAttributes]()   for attributes in cache {    if CGRectIntersectsRect(attributes.frame, rect) {     layoutAttributes.append(attributes)    }   }   return layoutAttributes  }  ``` 在上面的 `prepareLayout()` 中我们向 delegate 请求了 item 的高度:  ```swift let height = delegate.collectionView(collectionView!, heightForItemAtIndexPath: indexPath) 

这个 delegate 我们定义如下:

protocol PinterestLayoutDelegate {    func collectionView(collectionView: UICollectionView, heightForItemAtIndexPath indexPath: NSIndexPath) -> CGFloat  }

我们让 UICollectionViewController 对象来实现

extension PhotoStreamViewController: PinterestLayoutDelegate {    func collectionView(collectionView: UICollectionView, heightForItemAtIndexPath indexPath: NSIndexPath) -> CGFloat {     let random = arc4random_uniform(4) + 1     return CGFloat(random * 100)   } }

二、Layout Attributes

我们观察 Pinterest 每个 cell 都由一个 image 和一个 annotation 组成,因此我们需要知道 image height 和 annotation height,我们可以重新定义 delegate 方法来返回对应的 height。除此之外,我们创建自己的 Layout Attributes ,为其添加 photoHeight(因为每个 item 的 height 都是不同的),最后让 PinterestLayout 和 cell 应用我们设置的 Layout Attributes

1.首先修改 delegate 定义:

protocol PinterestLayoutDelegate {    func collectionView(collectionView: UICollectionView, heightForPhotoAtIndexPath indexPath: NSIndexPath, withWidth width: CGFloat) -> CGFloat   func collectionView(collectionView: UICollectionView, heightForAnnotationAtIndexPath indexPath: NSIndexPath, withWidth width: CGFloat) -> CGFloat  }

紧接着实现 delegate,我们给每个 annotation 返回了一个固定的 height:

extension PhotoStreamViewController: PinterestLayoutDelegate {    func collectionView(collectionView: UICollectionView, heightForPhotoAtIndexPath indexPath: NSIndexPath, withWidth width: CGFloat) -> CGFloat {     let random = arc4random_uniform(4) + 1     return CGFloat(random * 100)   }    func collectionView(collectionView: UICollectionView, heightForAnnotationAtIndexPath indexPath: NSIndexPath, withWidth width: CGFloat) -> CGFloat {     return 60   }  }

最后修改 prepareLayout 中调用到 delegate 方法的地方,此时的 height = photoHeight + annotationHeight

2.创建 UICollectionViewLayoutAttributes 的子类

按照文档说明,我们创建 UICollectionViewLayoutAttributes 的子类,要覆盖以下两个方法:

  • func copyWithZone(_ zone: NSZone) -> AnyObject collection view 会拷贝 layout attribute 对象,实现此方法确保自定义的属性被拷贝

  • func isEqual(_ anObject: AnyObject?) -> Bool 在 iOS 7 之后,只有 attributes 改变之后,collection view 才会应用,如何判断改变了呢,就要用到此方法,先实现自定义属性的判断,再调用 super

具体实现如下:

class PinterestLayoutAttributes: UICollectionViewLayoutAttributes {    var photoHeight: CGFloat = 0    override func copyWithZone(zone: NSZone) -> AnyObject {     let copy = super.copyWithZone(zone) as! PinterestLayoutAttributes     copy.photoHeight = photoHeight     return copy   }    override func isEqual(object: AnyObject?) -> Bool {     if let attributes = object as? PinterestLayoutAttributes {       if attributes.photoHeight == photoHeight {         return super.isEqual(object)       }     }     return false   }  }

3.让 PinterestLayout 应用我们设置的Layout Attributes

在 PinterestLayout 类中重写 layoutAttributesClass 方法

override class func layoutAttributesClass() -> AnyClass {     return PinterestLayoutAttributes.self   }

然后将 prepareLayout 中所有的 UICollectionViewLayoutAttributes 修改为 PinterestLayoutAttributes

4.让 cell 应用我们设置的Layout Attributes

为了应用我们自定义的 attributes,我们需要 UICollectionReusableView 对象实现 applyLayoutAttributes: 方法

override func applyLayoutAttributes(layoutAttributes: UICollectionViewLayoutAttributes!) {     super.applyLayoutAttributes(layoutAttributes)     let attributes = layoutAttributes as! PinterestLayoutAttributes     imageViewHeightLayoutConstraint.constant = attributes.photoHeight   }

三、Cell Content

最后我们来让照片等比例地缩放到 cell 的 imageView 中

Custom Collection View Layouts(一)

为了实现这一目标,我们可以很方便地使用 AVFoundation framework 提供的方法:

func AVMakeRectWithAspectRatioInsideRect(_ aspectRatio: CGSize, _ boundingRect: CGRect) -> CGRect

使用该方法用在 PinterestLayoutDelegate 的实现中,返回合适的 photoHeight:

extension PhotoStreamViewController: PinterestLayoutDelegate {    func collectionView(collectionView: UICollectionView, heightForPhotoAtIndexPath indexPath: NSIndexPath, withWidth width: CGFloat) -> CGFloat {     let photo = photos[indexPath.item]     let boundingRect = CGRect(x: 0, y: 0, width: width, height: CGFloat(MAXFLOAT))     let rect = AVMakeRectWithAspectRatioInsideRect(photo.image.size, boundingRect)     return rect.height   }    func collectionView(collectionView: UICollectionView, heightForAnnotationAtIndexPath indexPath: NSIndexPath, withWidth width: CGFloat) -> CGFloat {     return 60   }  }
-EOF-
正文到此结束
Loading...