防止在UICollectionView中“包装”项目

时间:2014-02-23 12:57:38

标签: ios uiscrollview uicollectionview uicollectionviewlayout contentsize

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

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

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

但这没有效果。问题:

  • 是否有一种简单的方法可以在不构建自定义布局的情况下实现此目的?
  • 使用自定义布局并覆盖collectionViewContentSize方法会成功获取集合视图以停止包装项目并向两个方向滚动吗?
  • 如果我必须构建一个自定义布局 - 我将不得不投入一些时间来学习 - 我是否将UICollectionViewLayout子类化,或者将UICollectionViewFlowLayout子类化以节省时间?

更新: 我尝试将UICollectionView嵌入为UIScrollView的子视图。集合视图本身表现正常 - 行没有包装在滚动视图的边缘,告诉我它正在填充我设置的UIScrollView内容大小。但是滚动视图不会在水平方向上平移,即它只是垂直滚动,集合视图无论如何都会自动进行。再次陷入困境。响应者链是否存在问题?

3 个答案:

答案 0 :(得分:5)

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

简单方法:使用UIScrollView和子类'UICollectionViewFlowLayout'。UICollectionView嵌入UIScrollView。在viewDiDLoad中设置滚动视图的contentSize属性以匹配集合视图将占用的完整大小(这将使默认流布局将项放在一个部分中而不包装)。子类UICollectionViewFlowLayout,并将该对象设置为集合视图的自定义布局。在自定义流布局中,覆盖collectionViewContentSize以返回集合视图矩阵的完整大小。使用此方法,您将使用流布局,但可以在两个方向上滚动以查看未包装的部分。缺点是你仍然有一个非常有限的流程布局。另外,将UICollectionView置于其自己的超类的实例中以获取集合视图本身应具有的功能似乎很笨拙。

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

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

答案 1 :(得分:0)

这是完整的矩阵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)
    }
}

部分是行, 项目是列

答案 2 :(得分:0)

以下版本的AndrewK代码更新为 Swift 4

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)

  }
}