集合视图中的动态高度单元格

时间:2016-04-29 20:16:33

标签: ios swift

我想像这个应用程序一样调整单元格的大小:

enter image description here

如您所见,右侧细胞比左侧短。为了实现同样的目的,我使用了以下代码:

var testI=1
func collectionView(collectionView: UICollectionView,
                    layout collectionViewLayout: UICollectionViewLayout,
                           sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
    var cellHeight=287
    if testI % 2 == 0 {
        cellHeight=247
    }
    testI += 1
    return CGSizeMake(CGFloat(176), CGFloat(cellHeight))
}

结果:

enter image description here

我不想要这些顶级空间。我该如何删除它们? 我的收藏视图是:

enter image description here

我该如何实现?

1 个答案:

答案 0 :(得分:9)

UICollectionViewFlowLayout期望有问题的单元具有相同的高度(或宽度取决于滚动方向),以便它可以正确地水平或垂直对齐它们。

因此,您无法使用它来获得上述效果。您需要创建自己的布局类,它具有与UICollectionViewFlowLayout类似的属性,例如minimumInteritemSpacingsectionInset等。此外,新的子类(我将命名为{{1}从现在开始)将能够为每个相应的单元格生成所有布局属性,其帧值具有不同的高度,以便单元格的位置就像您共享的图像一样。

在开始之前,您至少应该对如何创建自定义布局有一个基本的了解,因此我强烈建议您阅读this article以便重新开始。

首先,我们需要创建自己的UserCollectionViewLayout类,以便我们可以将单元格的高度存储在那里供以后使用。

UICollectionViewLayoutAttributes

布局类有时会在需要时复制class UserCollectionViewLayoutAttributes: UICollectionViewLayoutAttributes { var height: CGFloat = 0.0 } ,因此我们需要覆盖几个方法,以便在发生时将UICollectionViewLayoutAttributes传递给复制的对象:

height

我们需要一个新的协议,要求代表每个单元格的高度:

(与override func copyWithZone(zone: NSZone) -> AnyObject { let copy = super.copyWithZone(zone) as! UserCollectionViewLayoutAttributes copy.height = height return copy } override func isEqual(object: AnyObject?) -> Bool { if let object = object as? UserCollectionViewLayoutAttributes { if height != object.height { return false } return super.isEqual(object) } return false } 类似)

UICollectionViewDelegateFlowLayout.sizeForItemAtIndexPath

好的,我们在定义布局的过程中需要的一切都在那里。那么,让我们开始吧:

protocol UserCollectionViewLayoutDelegate: UICollectionViewDelegate {
  func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UserCollectionViewLayout, heightForItemAtIndexPath indexPath: NSIndexPath) -> CGFloat 
}

好的,让我们一块一块地解释上面的属性:

  • class UserCollectionViewLayout: UICollectionViewLayout { private var contentHeight: CGFloat = 0.0 private var allAttributes = [UserCollectionViewLayoutAttributes]() weak var delegate: UserCollectionViewLayoutDelegate! var numberOfColumns: Int = 2 private var numberOfItems: Int { return collectionView?.numberOfItemsInSection(0) ?? 0 } private var contentWidth: CGFloat { return CGRectGetWidth(collectionView?.bounds ?? CGRectZero) } private var itemWidth: CGFloat { return round(contentWidth / CGFloat(numberOfColumns)) } ... :集合视图的内容高度,因此它将知道如何/在哪里滚动。与定义contentHeight相同。
  • scrollView.contentSize.height:包含每个单元格属性的数组。它将以prepareLayout()方法填充。
  • allAttributes:一个值,表示每行显示的单元格数。您的示例中将numberOfColumns,因为您要连续显示2张照片。

让我们继续覆盖一些重要的方法:

2

关键部分是正确实施override func collectionViewContentSize() -> CGSize { return CGSize(width: contentWidth, height: contentHeight) } override class func layoutAttributesClass() -> AnyClass { return UserCollectionViewLayoutAttributes.self } 。在此方法中,我们将填充与每个indexPath对应的所有布局属性,并将它们存储在prepareLayout中以供以后使用。

布局看起来像是2D单元格。但是,每个细胞的高度可能不同。因此,您应该记住,位于同一行的单元格的allAttributes值可能不相同:

frame.origin.y

我们需要实现另外两种方法让布局知道在显示一个单元格时应该使用哪个布局属性:

让我们试一试:

override func prepareLayout() {
  if allAttributes.isEmpty {
    let xOffsets = (0..<numberOfColumns).map { index -> CGFloat in
      return CGFloat(index) * itemWidth
    }
    var yOffsets = Array(count: numberOfColumns, repeatedValue: CGFloat(0))
    var column = 0

    for item in 0..<numberOfItems {
      let indexPath = NSIndexPath(forItem: item, inSection: 0)
      let attributes = UserCollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)

      let cellHeight = delegate.collectionView(collectionView!, layout: self, heightForItemAtIndexPath: indexPath)

      let frame = CGRect(x: xOffsets[column], y: yOffsets[column], width: itemWidth, height: cellHeight)

      attributes.frame = frame
      attributes.height = cellHeight

      allAttributes.append(attributes)

      yOffsets[column] = yOffsets[column] + cellHeight

      column = (column >= numberOfColumns - 1) ? 0 : column + 1
      contentHeight = max(contentHeight, CGRectGetMaxY(frame))
    }
  }
}

override func invalidateLayout() {
  allAttributes.removeAll()
  contentHeight = 0
  super.invalidateLayout()
}   

好的,这就是布局部分的全部内容。现在,我们有一个布局类,能够在一行中显示具有不同高度值的单元格。现在,我们需要弄清楚如何将override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { return cachedAttributes.filter { attribute -> Bool in return CGRectIntersectsRect(rect, attribute.frame) } } override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? { return cachedAttributes.filter { attribute -> Bool in return attribute.indexPath == indexPath }.first } 值传递给实际的单元格对象。

幸运的是,如果您使用height组织单元格内容,则非常简单。 AutoLayout中有一个名为applyLayoutAttributes的方法,可让您对单元格的布局进行最后一分钟更改。

UICollectionViewCell

将所有这些结合在一起,结果如下:

enter image description here

我确信您可以像class UserCell: UICollectionViewCell { let userView = UIView(frame: CGRectZero) private var heightLayoutConstraint: NSLayoutConstraint! override func applyLayoutAttributes(layoutAttributes: UICollectionViewLayoutAttributes) { super.applyLayoutAttributes(layoutAttributes) let attributes = layoutAttributes as! UserCollectionViewLayoutAttributes heightLayoutConstraint.constant = attributes.height } override init(frame: CGRect) { super.init(frame: frame) contentView.backgroundColor = UIColor.whiteColor() contentView.addSubview(userView) userView.translatesAutoresizingMaskIntoConstraints = false contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat( "H:|[userView]|", options: NSLayoutFormatOptions.AlignAllCenterY, metrics: nil, views: ["userView": userView]) ) contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat( "V:|[userView]|", options: NSLayoutFormatOptions.AlignAllCenterX, metrics: nil, views: ["userView": userView]) ) heightLayoutConstraint = NSLayoutConstraint( item: self, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 0.0, constant: 0.0 ) heightLayoutConstraint.priority = 500 contentView.addConstraint(heightLayoutConstraint) } } 类一样添加minimumInteritemSpacingsectionInset属性,以便在单元格之间添加一些填充。

(提示:你应该相应调整UICollectionViewFlowLayout方法中的单元格框架。)

您可以从here下载示例项目。