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

Tol*_*lar 2 ios swift

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

在此输入图像描述

如您所见,右侧单元格比左侧短.为了达到同样的效果,我使用了以下代码:

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))
}
Run Code Online (Sandbox Code Playgroud)

结果:

在此输入图像描述

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

在此输入图像描述

我怎么能做到这一点?

ozg*_*gur 10

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

因此,您无法使用它来获得上述效果.您需要创建这将有类似的性质作为自己的布局类UICollectionViewFlowLayout,如minimumInteritemSpacing,sectionInset等.此外,新的子类(我将其命名为 UserCollectionViewLayout从现在开始)将能够生成与帧值分别对应小区内的所有布局属性具有不同的高度,以便细胞定位就像您共享的图像一样.

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

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

class UserCollectionViewLayoutAttributes: UICollectionViewLayoutAttributes {
  var height: CGFloat = 0.0
}
Run Code Online (Sandbox Code Playgroud)

布局类有时会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
}
Run Code Online (Sandbox Code Playgroud)

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

(相似UICollectionViewDelegateFlowLayout.sizeForItemAtIndexPath)

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

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

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))
  }
  ...
Run Code Online (Sandbox Code Playgroud)

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

  • contentHeight:集合视图的内容高度,因此它将知道如何/在哪里滚动.与定义相同scrollView.contentSize.height.
  • allAttributes:包含每个单元格属性的数组.它将填入prepareLayout()方法中.
  • numberOfColumns:一个值,指示每行将显示多少个单元格.它将2在您的示例中,因为您要连续显示2张照片.

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

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

override class func layoutAttributesClass() -> AnyClass {
  return UserCollectionViewLayoutAttributes.self
}
Run Code Online (Sandbox Code Playgroud)

关键是要prepareLayout正确实施.在此方法中,我们将填充与每个indexPath对应的所有布局属性,并将其存储以allAttributes供以后使用.

布局看起来像2D单元格.但是,每个细胞的高度可能不同.因此,您应该记住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()
}   
Run Code Online (Sandbox Code Playgroud)

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

让我们试一试:

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
}
Run Code Online (Sandbox Code Playgroud)

好的,这就是布局部分的全部内容.现在,我们有一个布局类,能够在一行中显示具有不同高度值的单元格.现在,我们需要弄清楚如何将height值传递给实际的单元对象.

对我们来说幸运的是,如果你正在使用组织你的单元格内容,这是非常简单的AutoLayout.有一个命名的方法applyLayoutAttributesUICollectionViewCell,使您能够做出格的布局最后一分钟的变化.

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)
  }
}
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

我相信你可以像在课堂上一样添加minimumInteritemSpacingsectionInset属性UICollectionViewFlowLayout,以便在单元格之间添加一些填充.

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

您可以从此处下载示例项目.