防止在UICollectionView中“包装”项目

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

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

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

但这没有效果。 问题:

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

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

找出两种方法来做到这一点。 两者都需要自定义布局。 问题是默认的流布局 – 我现在从Collection View Programming Guide知道这部分是流布局的定义 – 根据superview的边界生成单元布局属性,并将项目包装在将它们保持在边界内的部分,以便仅在一个轴上进行滚动。 将跳过代码细节,因为它并不难,我的问题主要是对采取什么方法的困惑。

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

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

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

这是完整的矩阵customLayout:

 import UIKit class MatrixLayout: UICollectionViewLayout { var itemSize: CGSize! var interItemSpacingY: CGFloat! var interItemSpacingX: CGFloat! var layoutInfo: Dictionary! 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() 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() 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) } } 

部分是行,项目是列

这是一个更新为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.. 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() 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..