7

我需要一个UICollectionView在宽度和高度上都可能大于可见框架的网格,同时保持行和列的完整性。默认UICollectionViewFlowLayout允许部分滚动到屏幕外,但它将项目包装在一个部分中以使它们全部显示在屏幕上,这搞砸了我的网格。

认识到这UICollectionView是一个子类UIScrollView,我尝试在 viewDidLoad 中手动设置集合视图的内容大小属性:

self.collectionView.contentSize = CGSizeMake((columns * (cellWidth + itemSpacingX), (rows * (cellHeight + itemSpacingY));

但这没有任何效果。问题:

  • 有没有一种简单的方法可以在不构建自定义布局的情况下实现这一点?
  • 使用自定义布局并覆盖该collectionViewContentSize方法是否会成功让集合视图停止包装项目并在两个方向上滚动?
  • 如果我必须构建一个自定义布局——我将不得不花一些时间来学习——我是子类化UICollectionViewLayout还是子类化会UICollectionViewFlowLayout节省时间?

更新:我尝试将 UICollectionView 嵌入为 UIScrollView 的子视图。集合视图本身的行为是正确的——行没有在滚动视图的边缘换行,告诉我它正在填充我设置的 UIScrollView 内容大小。但是滚动视图不会在水平方向上平移,即它只会垂直滚动,而集合视图本身就是这样做的。于是又卡住了。响应者链可能有问题吗?

4

3 回答 3

5

想出了两种方法来做到这一点。两者都需要自定义布局。问题是默认的流布局——我现在从集合视图编程指南中知道,这部分是流布局的定义——根据超级视图的边界生成单元布局属性,并将项目包装在一个部分以将它们保持在界限内,以便仅在一个轴上发生滚动。将跳过代码细节,因为它并不难,我的问题主要是对采用什么方法感到困惑。

简单的方法:使用 UIScrollView 和子类 'UICollectionViewFlowLayout'。嵌入UICollectionView在一个UIScrollView. 设置滚动视图的 contentSize 属性viewDiDLoad以匹配您的集合视图将占据的完整大小(这将使默认的流布局将项目放置在一个部分内的单行中而无需换行)。Subclass UICollectionViewFlowLayout,并将该对象设置为集合视图的自定义布局。在自定义流布局中,覆盖collectionViewContentSize以返回集合视图矩阵的完整大小。使用这种方法,您将使用流式布局,但可以双向滚动以查看未包装的部分。缺点是您仍然有一个非常有限的流布局。另外,放一个UICollectionView在它自己的超类的实例中只是为了获得集合视图本身应该具有的功能。

更难的方式,但更通用和优雅:子类 UICollectionViewLayout。 我使用本教程来学习如何实现一个完整的自定义布局。你不需要UIScrollView这里。如果您放弃流布局、子类UICollectionViewLayout并将其设置为自定义布局,您可以构建矩阵并从集合视图本身获得正确的行为。这是更多的工作,因为您必须生成所有布局属性,但您将被定位以使集合视图做任何您想做的事情。

在我看来,Apple 应该在默认流布局中添加一个抑制换行的属性。让设备显示具有完整行和列的 2D 矩阵并不是一个奇特的功能,而且看起来应该更容易做到。

于 2014-03-02T20:40:02.970 回答
4

这是更新到Swift 4的 AndrewK 代码版本:

import UIKit

class CollectionViewMatrixLayout: UICollectionViewLayout {

  var itemSize: CGSize
  var interItemSpacingY: CGFloat
  var interItemSpacingX: CGFloat
  var layoutInfo: [IndexPath: UICollectionViewLayoutAttributes]

  override init() {
    itemSize = CGSize(width: 50, height: 50)
    interItemSpacingY = 1
    interItemSpacingX = 1
    layoutInfo = [IndexPath: UICollectionViewLayoutAttributes]()

    super.init()
  }

  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override func prepare() {
    guard let collectionView = self.collectionView else {
      return
    }

    var cellLayoutInfo = [IndexPath: UICollectionViewLayoutAttributes]()
    var indexPath = IndexPath(item: 0, section: 0)

    let sectionCount = collectionView.numberOfSections
    for section in 0..<sectionCount {

      let itemCount = collectionView.numberOfItems(inSection: section)
      for item in 0..<itemCount {
        indexPath = IndexPath(item: item, section: section)
        let itemAttributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
        itemAttributes.frame = frameForCell(at: indexPath)
        cellLayoutInfo[indexPath] = itemAttributes
      }

      self.layoutInfo = cellLayoutInfo
    }
  }

  func frameForCell(at indexPath: IndexPath) -> CGRect {
    let row = indexPath.section
    let column = indexPath.item

    let originX = (itemSize.width + interItemSpacingX) * CGFloat(column)
    let originY = (itemSize.height + interItemSpacingY) * CGFloat(row)

    return CGRect(x: originX, y: originY, width: itemSize.width, height: itemSize.height)
  }

  override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
  {
    var allAttributes = Array<UICollectionViewLayoutAttributes>()

    for (_, attributes) in self.layoutInfo {
      if (rect.intersects(attributes.frame)) {
        allAttributes.append(attributes)
      }
    }
    return allAttributes
  }

  override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    return self.layoutInfo[indexPath]
  }

  override var collectionViewContentSize: CGSize {
    guard let collectionView = self.collectionView else {
      return .zero
    }

    let sectionCount = collectionView.numberOfSections
    let height = (itemSize.height + interItemSpacingY) * CGFloat(sectionCount)

    let itemCount = Array(0..<sectionCount)
      .map { collectionView.numberOfItems(inSection: $0) }
      .max() ?? 0

    let width  = (itemSize.width + interItemSpacingX) * CGFloat(itemCount)

    return CGSize(width: width, height: height)

  }
}
于 2018-04-20T17:20:23.033 回答
1

这是完整的矩阵customLayout:

import UIKit

class MatrixLayout: UICollectionViewLayout {

    var itemSize: CGSize!
    var interItemSpacingY: CGFloat!
    var interItemSpacingX: CGFloat!
    var layoutInfo: Dictionary<NSIndexPath, UICollectionViewLayoutAttributes>!

    required init?(coder aDecoder: NSCoder) {

        super.init(coder: aDecoder)

        itemSize = CGSizeMake(50.0, 50.0)
        interItemSpacingY = 1.0
        interItemSpacingX = 1.0
    }

    override func prepareLayout() {

        var cellLayoutInfo = Dictionary<NSIndexPath, UICollectionViewLayoutAttributes>()

        let sectionCount = self.collectionView?.numberOfSections()
        var indexPath = NSIndexPath(forItem: 0, inSection: 0)

        for (var section = 0; section < sectionCount; section += 1)
        {
            let itemCount = self.collectionView?.numberOfItemsInSection(section)

            for (var item = 0; item < itemCount; item += 1)
            {
                indexPath = NSIndexPath(forItem:item, inSection: section)
                let itemAttributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
                itemAttributes.frame = frameForCellAtIndexPath(indexPath)
                cellLayoutInfo[indexPath] = itemAttributes
            }

            self.layoutInfo = cellLayoutInfo
        }
    }

    func frameForCellAtIndexPath(indexPath: NSIndexPath) -> CGRect
    {
        let row = indexPath.section
        let column = indexPath.item

        let originX = (self.itemSize.width + self.interItemSpacingX) * CGFloat(column)
        let originY = (self.itemSize.height + self.interItemSpacingY) * CGFloat(row)

        return CGRectMake(originX, originY, self.itemSize.width, self.itemSize.height)
    }

    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]?
    {
        var allAttributes = Array<UICollectionViewLayoutAttributes>()

        for (index, attributes) in self.layoutInfo
        {
            if (CGRectIntersectsRect(rect, attributes.frame))
            {
                allAttributes.append(attributes)
            }
        }
        return allAttributes
    }

    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
        return self.layoutInfo[indexPath]
    }


    override func collectionViewContentSize() -> CGSize {

        let width:CGFloat = (self.itemSize.width + self.interItemSpacingX) * CGFloat((self.collectionView?.numberOfItemsInSection(0))!)
        let height:CGFloat = (self.itemSize.height + self.interItemSpacingY) * CGFloat((self.collectionView?.numberOfSections())!)

        return CGSizeMake(width, height)
    }
}

部分是行,项目是列

于 2016-04-14T14:57:35.357 回答