简体   繁体   English

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

[英]Dynamic-height cell in collection view

I want to resize cells like this app: 我想调整像这个应用程序的单元格:

在此输入图像描述

As you can see, right cells are shorter than left. 如您所见,右侧单元格比左侧短。 To achive same thing i used following code: 为了达到同样的效果,我使用了以下代码:

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))
}

Result: 结果:

在此输入图像描述

I dont want these top spaces. 我不想要这些顶级空间。 How can I remove them? 我该如何删除它们? My collection view attrs: 我的收藏品视图:

在此输入图像描述

How can I achive that? 我怎么能做到这一点?

UICollectionViewFlowLayout expects cells in question to have the same height (or width depending on scroll direction) so that it can correctly align them horizontally or vertically. UICollectionViewFlowLayout单元格具有相同的高度(或宽度取决于滚动方向),以便它可以水平或垂直正确对齐它们。

Thus, you can't use that to get the above effect. 因此,您无法使用它来获得上述效果。 You need to create your own layout class which will have similar properties as UICollectionViewFlowLayout such as minimumInteritemSpacing , sectionInset etc. Besides, the new subclass (which I will name UserCollectionViewLayout from now on) will be capable of generating all the layout attributes for each corresponding cell with a frame value having different height so that cells will be positioned just like in the image you've shared. 您需要创建自己的布局类,它具有与UICollectionViewFlowLayout类似的属性,例如minimumInteritemSpacingsectionInset等。此外,新的子类(我将从现在开始将其命名为UserCollectionViewLayout将能够为每个相应的单元格生成所有布局属性具有不同高度的帧值,以便单元格的位置与您共享的图像一样。

Before we begin, you should at least have a basic understanding about how to create custom layouts so I strongly recommend you to read this article for a fresh start. 在开始之前,您至少应该对如何创建自定义布局有一个基本的了解,因此我强烈建议您阅读本文以便重新开始。

First, we need to create our own UICollectionViewLayoutAttributes class so we can store the height of the cell there for later use. 首先,我们需要创建自己的UICollectionViewLayoutAttributes类,以便我们可以在那里存储单元格的高度以供以后使用。

class UserCollectionViewLayoutAttributes: UICollectionViewLayoutAttributes {
  var height: CGFloat = 0.0
}

Layout classes sometimes copy UICollectionViewLayoutAttributes when needed, so we need to override several methods to be able to pass height to copied object when it happens: 布局类有时会在需要时复制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
}

We are going to need a new protocol which will ask for delegate the height of each cell: 我们将需要一个新的协议,它将要求代表每个单元格的高度:

(Similar to UICollectionViewDelegateFlowLayout.sizeForItemAtIndexPath ) (与UICollectionViewDelegateFlowLayout.sizeForItemAtIndexPath类似)

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

OK, everything we need on our way to defining the layout is there. 好的,我们在定义布局的过程中需要的一切就在那里。 So, let's start: 那么,让我们开始吧:

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))
  }
  ...

OK, let's explain the above properties piece by piece: 好的,让我们一块一块地解释上面的属性:

  • contentHeight : Content height of collection view so it will know how to/where to scroll. contentHeight :集合视图的内容高度,因此它将知道如何/在哪里滚动。 Same as defining scrollView.contentSize.height . 与定义scrollView.contentSize.height相同。
  • allAttributes : An array holding the attributes of each cell. allAttributes :包含每个单元格属性的数组。 It will be populated in prepareLayout() method. 它将填充在prepareLayout()方法中。
  • numberOfColumns : A value indicating how many cells will be shown at each row. numberOfColumns :一个值,指示每行将显示多少个单元格。 It will be 2 in your example as you want to show 2 photos in a row. 在你的例子中它将是2 ,因为你想连续显示2张照片。

Let's continue with overriding some important methods: 让我们继续覆盖一些重要的方法:

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

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

The crucial part is to implement prepareLayout correctly. 关键部分是正确实现prepareLayout In this method, we are going to populate all the layout attributes corresponding to each indexPath, and store them in allAttributes for later use. 在此方法中,我们将填充与每个indexPath对应的所有布局属性,并将它们存储在allAttributes以供以后使用。

The layout looks like a 2D matrix of cells. 布局看起来像2D单元格。 However, height of each cell might be different. 但是,每个细胞的高度可能不同。 Thus, you should keep in mind that frame.origin.y value might not be same for cells positioned in the same row: 因此,您应该记住,对于位于同一行的单元格, 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()
}   

We need to implement 2 more methods to let the layout know which layout attribute it should use upon displaying a cell which are: 我们需要实现另外两个方法,让布局知道在显示单元格时应该使用哪个布局属性:

Let's give it a shot: 让我们试一试:

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
}

OK, that's all for the layout part. 好的,这就是布局部分的全部内容。 Right now, we have a layout class capable of displaying cells with different height values in a single row. 现在,我们有一个布局类,能够在一行中显示具有不同高度值的单元格。 Now, we need to figure out how to pass height value to actual cell object. 现在,我们需要弄清楚如何将height值传递给实际的单元格对象。

Luckily for us, it is really straightforward if you are organizing your cell content using AutoLayout . 幸运的是,如果您使用AutoLayout组织单元格内容,则非常简单。 There is a method named applyLayoutAttributes in UICollectionViewCell which enables you to make last minute changes on cell's layout. 有一个名为方法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)
  }
}

Getting all of them together, here is the result: 将所有这些结合在一起,结果如下:

在此输入图像描述

I am sure you can add minimumInteritemSpacing and sectionInset properties just like in UICollectionViewFlowLayout class in order to add some padding between cells. 我相信你可以像在UICollectionViewFlowLayout类中一样添加minimumInteritemSpacingsectionInset属性,以便在单元格之间添加一些填充。

(Hint: you should tweak cell's frame in prepareLayout method accordingly.) (提示:你应该相应地调整prepareLayout方法中的单元格框架。)

You can download the example project from here . 您可以从此处下载示例项目。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 动态高度 UITableViewCell 内的动态高度 UICollectionView - Dynamic-height UICollectionView inside a dynamic-height UITableViewCell 设置集合视图单元格的动态宽度和高度 - set Dynamic width and height of collection view cell 自定义集合视图布局的动态单元格高度 - Dynamic Cell Height for Custom Collection View Layout 使用自定义集合视图布局快速配置动态单元格高度 - Configuring dynamic cell height with a custom collection view layout swift 使用自动布局的自定义集合视图单元格的动态高度 - Dynamic height for custom collection view cell by using autolayouts 在动态高度UITableViewCell出现后更改其高度 - Changing the height of a dynamic-height UITableViewCell after it's already appeared 退出表格视图单元格后填充的嵌入式集合视图的动态表格视图单元格高度 - Dynamic table view cell height for embedded collection view that is filled after dequeue of table view cell 自动收集视图单元格高度 - Auto collection view cell Height iOS tableview具有动态高度单元格,但所有单元格一次显示 - iOS tableview with dynamic-height cells but all cells are displayed at once 是否可以通过其动态单元格高度更改集合视图的高度? 如果可能,我们应该使用哪种委托方法? - Is it possible to change the height of collection view with its dynamic cell height? which delegate method should we use if possible?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM